詳解Javascript 函數聲明和函數表達式的區別


  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函數執行的主要步驟示意圖。

  上圖為sayHey函數執行主要步驟的示意圖。若對閉包感興趣,可以看另外一篇博文http://www.cnblogs.com/isaboy/p/javascript_closure.html。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM