1. 程式人生 > >函式宣告與函式表示式不同2

函式宣告與函式表示式不同2

  Javascript Function無處不在,而且功能強大!通過Javascript函式可以讓JS具有面向物件的一些特徵,實現封裝、繼承等,也可以讓程式碼得到複用。但事物都有兩面性,Javascript函式有的時候也比較“任性”,你如果不瞭解它的“性情”,它很可能給你製造出一些意想不到的麻煩(bugs)出來。  

  Javascript Function有兩種型別:

  1)函式宣告(Function Declaration);

    // 函式宣告
    function funDeclaration(type){
        return type==="Declaration";
    }

  2)函式表示式(Function Expression)。

    // 函式表示式
    var funExpression = function(type){
        return type==="Expression";
    }

  上面的程式碼看起來很類似,感覺也沒什麼太大差別。但實際上,Javascript函式上的一個“陷阱”就體現在Javascript兩種型別的函式定義上。下面看兩段程式碼(分別標記為程式碼1段和程式碼2段):

1     funDeclaration("Declaration");//=> true
2     function funDeclaration(type){
3 return type==="Declaration"; 4 }
1     funExpression("Expression");//=>error
2     var funExpression = function(type){
3         return type==="Expression";
4     }
  用函式宣告建立的函式funDeclaration可以在funDeclaration定義之前就進行呼叫;而用函式表示式建立的funExpression函式不能在funExpression被賦值之前進行呼叫。
為什麼會這樣呢?!這就要理解Javascript Function兩種型別的區別:
函式宣告建立的函式可以在函式解析後呼叫(解析時進行等邏輯處理);而用函式表示式建立的函式是在執行時進行賦值,且要等到表示式賦值完成後才能呼叫。
這個區別看似微小,但在某些情況下確實是一個難以發現的陷阱。出現這個陷阱的本質原因體現在這兩種型別在Javascript function hoisting(函式提升)和執行時機(解析時/執行時)上的差異。關於變數提升,可參見我的另外一篇博文http://www.cnblogs.com/isaboy/p/javascript_hoisting.html。上面兩段程式碼的函式提升可示意為下圖:

程式碼1段JS函式等同於:
    function funDeclaration(type){
        return type==="Declaration";
    }
    funDeclaration("Declaration");//=> true
程式碼2段JS函式等同於:
    var funExpression;
    funExpression("Expression");//==>error
    funExpression = function(type){
        return type==="Expression";
    }

  上述程式碼在執行時,只定義了funExpression變數,但值為undefined。因此不能在undefined上進行函式呼叫。此時funExpression賦值語句還沒執行到。為了進一步加深JS函式兩種型別的區別,下面給出一個更具迷惑性的示例,請看下面的程式碼(程式碼段4):

複製程式碼
 1     var sayHello;
 2     console.log(typeof (sayHey));//=>function    
 3     console.log(typeof (sayHo));//=>undefined
 4     if (true) {
 5         function sayHey() {
 6             console.log("sayHey");
 7         }
 8         sayHello = function sayHo() {
 9             console.log("sayHello");
10     }
11     } else {
12         function sayHey() {
13             console.log("sayHey2");
14         }
15         sayHello = function sayHo() {
16             console.log("sayHello2");
17         }
18     }    
19     sayHey();// => sayHey2    
20     sayHello();// => sayHello
複製程式碼

  分析
:sayHey是用函式宣告建立的,在JS解析時JS編譯器將函式定義進行了函式提升,也就是說,在解析JS程式碼的時候,JS編譯器(條件判斷不形成新的作用域,兩個sayHey函式定義都被提升到條件判斷之外)檢測到作用域內有兩個同名的sayHey定義,第一個定義先被提升,第二個定義接著被提升(第二個定義在第一個定義之下),第二個定義覆蓋了第一個sayHey定義,所以sayHey()輸出sayHey2;而sayHello是用函式表示式建立的,其表示式的內容是在JS執行時(不是解析時)才能確定(這裡條件判斷就起到作用了),所以sayHello表示式執行了第一個函式定義並賦值,則sayHello()輸出sayHello。

  程式碼段4的程式碼實際上等同於下面的程式碼(程式碼段5):

複製程式碼
 1     var sayHello;
 2     function sayHey() {
 3             console.log("sayHey");
 4         }
 5     function sayHey() {
 6             console.log("sayHey2");
 7     }
 8     console.log(typeof (sayHey));//=>function    
 9     console.log(typeof (sayHo));//=>undefined
10     if (true) {
11         //hoisting...
12         sayHello = function sayHo() {
13             console.log("sayHello");
14     }
15     } else {
16         //hoisting...
17         sayHello = function sayHo() {
18             console.log("sayHello2");
19         }
20     }    
21     sayHey();// => sayHey2    
22     sayHello();// => sayHello
複製程式碼

  有的人也許會懷疑函式sayHey的定義是第二個覆蓋第一個了麼?我們可以把sayHey的原始碼進行輸出,有圖有真相,如下圖所示:

  總結

  Javascript 中函式宣告和函式表示式是存在區別的,函式宣告在JS解析時進行函式提升,因此在同一個作用域內,不管函式宣告在哪裡定義,該函式都可以進行呼叫。而函式表示式的值是在JS執行時確定,並且在表示式賦值完成後,該函式才能呼叫。這個微小的區別,可能會導致JS程式碼出現意想不到的bug,讓你陷入莫名的陷阱中。

  最後附上程式碼段4中sayHello和sayHey兩個函式的核心步驟(個人理解,若有異議歡迎留言探討):

  上圖為sayHello函式執行的主要步驟示意圖。