JavaScript 之有趣的函式(函式宣告、呼叫、預解析、作用域)

七七呢發表於2019-07-08

前言:“函式是物件,函式名是指標。”,函式名僅僅是指向函式的指標,與其他包含函式指標的變數沒有什麼區別,話句話說,一個函式可能有多個名字。

 

-1.函式宣告,function+函式名稱。呼叫方法:函式名(引數);

function f1(x,y){
    return x+y;     //函式體
}
console.log(f1(2,3));

這是最常見的指定函式名宣告函式,在函式體內返回引數值,函式呼叫時才會輸出結果。既然說到函式,那就免不了提一提它的預解析以及作用域。

此類方法定義的函式,在程式碼開始執行之前會通過直譯器進行一個函式宣告提前的過程,並將其新增到執行環境中, JavaScript 引擎會將其提升到程式碼樹的頂端,率先執行,所以即使宣告函式的程式碼在函式呼叫程式碼的後面,也能正確訪問,上述過程就被稱為函式的預解析。例如:

console.log(sum);          //控制檯輸出函式原始碼,證明函式可以被呼叫
console.log(sum(3,2));     // 5
function sum(x,y){
    return x+y;
}

執行環境定義了變數或者函式有權訪問其他資料,每個環境中都有一個與之關聯的變數物件,環境中定義的變數和函式都儲存在這個物件中,解析器在處理資料時就會使用這個物件。

 

- 2.匿名函式,即沒有命名的函式,通過給將函式賦值給變數的形式宣告函式,也稱之為函式表示式。呼叫方法:函式名(引數);

var f2 = function(x,y){
                return x+y;
            }
f2(4,5);                

此類方法定義的函式不能在函式宣告前呼叫函式,因為在預解析機制中:

1.把變數的宣告提升到當前作用域的最前面,只會提升宣告,不會提升賦值。 

2.把函式的宣告提升到當前作用域的最前面,只會提升宣告,不會提升呼叫。

3.先提升var,在提升function 

所以上述程式碼如果提前呼叫函式,在預解析的執行環境中是:

var f2;       //變數宣告提升,但賦值沒有
f2(4,5);     //報錯
console.log(f2(4,5));
f2 = function(x,y){
   return x+y;
}

匿名函式是用一個變數去接收函式,因此預解析只將變數的宣告提前了,此時的函式位於一個初始化語句中,在執行到函式體程式碼之前變數不會儲存對函式的引用,所以直接報錯。

 

-3.自呼叫函式,顧名思義就是自己呼叫自己的函式,在宣告的同時進行呼叫,雖然方便但只能執行一次。

(function(){
      console.log("這是一個自呼叫函式");
})();
/* * * 而它的演變過程也很簡單,是由函式表示式演變而來。 * var f1 = function(){ * console.log("這是一個自呼叫函式"); * } * f1(); * 這裡把呼叫時的 f1 替換成 function(){} * 所以呼叫時就是function(){}(); * 為了保持程式碼的整體性,所以在最外層加了一個括號。 * * */

//這裡還有一個點需要注意,自呼叫函式前的一個函式如果沒有輸出呼叫,需要在函式表示式的結尾加上分號,以和自呼叫函式區分開來,防止將上一個函式也解析成自呼叫函式。

 

 

-4.Function 建構函式,Function 建構函式可以接受任意數量的引數,但最後一個引數始終被看做是函式體,而前面則看作是函式體的引數。呼叫:函式名();

var sum = new Function("sum1","sum2","return sum1+sum2");

 但這種定義函式的方法並不推薦使用,因為在此類函式在執行時會先解析一次常規ECMAscript 程式碼,再解析傳入函式中的字串,反覆呼叫時將會影響效能。

 函式也是有資料型別的,所有函式的型別都是 function 。 

 

關於作用域鏈

作用域鏈的用途是,保證執行環境中,有權訪問的所有函式和變數的有序訪問。簡單來說就是變數的使用範圍。

 

全域性變數:在函式以外,用 var 宣告的變數都是全域性變數,在全域性執行環境中都可以使用。 !!! 但需要注意,全域性變數只有在整個程式退出或者銷燬後才會被釋放,否則就一直佔記憶體。

區域性變數:在函式內部定義的變數就是區域性變數,只能在函式內部使用。

隱式全域性變數:沒有var 宣告的,也是作用於全域性,但是可以使用delete刪除。

全域性作用域:全域性變數的使用範圍,始終是作用域鏈中的最後一個物件。

區域性作用域:區域性變數的使用範圍。

塊級作用域:指的是在一對大括號內宣告的變數,就只能在這對大括號中使用,但是js中全部都可以使用,所以js沒有塊級作用域,函式除外。

 

使用過程:1.作用域鏈的前端始終是當前執行程式碼所在環境的變數物件,解析過程是按照作用域鏈一級一級搜尋的過程。

     2.始終是從作用域的前端開始的,然後逐級向後回溯。

     3.內部變數可以通過作用域鏈訪問外部變數,但外部變數不能訪問任何內部的變數或者函式。(自內向外訪問)

 

最後給大家舉個栗子,綜合解說函式呼叫、預解析以及作用域:

f1();
   console.log(c);
   console.log(b);
   console.log(a);  
   function f1(){
      var a = b = c = 9;
        console.log(a);
        console.log(b);
        console.log(c);
   }

輸出的結果為:9  9  9  9  9  報錯,原因如下

function f1(){
       // var a = b = c = 9;   變數的宣告也提升
       var a;
       a = 9;  // 此時的 a 是被 var 宣告的,作為函式內的區域性變數,只能在函式內被呼叫,所以最後的a會報錯
       b = 9;  
       c = 9;  // b,c 則是隱式全域性變數
       console.log(a);
       console.log(b);
       console.log(c);
}
f1();  //呼叫f1函式,預解析後f1函式的宣告提升到作用域的最前面,此時的程式碼為
console.log(c);
console.log(b);
console.log(a); 

第二個小栗子

f1();
var f1 = function (){
            console.log(a);
            var a = 10;
        };
//結果是報錯 //因為預解析的存在,所以函式和變數提前 //由於是函式表示式的形式,所以預解析後的程式碼為:

// var f1; // f1(); // function (){ // console.log(a); // var a = 10; // };

 

  

 

相關文章