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

Javascript Function無處不在,並且功能強大!經過Javascript函數可讓JS具備面向對象的一些特徵,實現封裝、繼承等,也可讓代碼獲得複用。但事物都有兩面性,Javascript函數有的時候也比較「任性」,你若是不瞭解它的「性情」,它極可能給你製造出一些意想不到的麻煩(bugs)出來。  javascript

Javascript Function有兩種類型:html

1)函數聲明(Function Declaration);java

// 函數聲明
    function funDeclaration(type){
        return type==="Declaration";
    }

2)函數表達式(Function Expression)。閉包

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

上面的代碼看起來很相似,感受也沒什麼太大差異。但實際上,Javascript函數上的一個「陷阱」就體如今Javascript兩種類型的函數定義上。下面看兩段代碼(分別標記爲代碼1段和代碼2段):函數

funDeclaration("Declaration");//=> true
    function funDeclaration(type){
      return type==="Declaration";
  }
funExpression("Expression");//=>error
      var funExpression = function(type){
        return type==="Expression";
   }
用函數聲明建立的函數funDeclaration能夠在funDeclaration定義以前就進行調用;
而用函數表達式建立的funExpression函數不能在funExpression被賦值以前進行調用。
爲何會這樣呢?!這就要理解Javascript Function兩種類型的區別:
用函數聲明建立的函數能夠在函數解析後調用(解析時進行等邏輯處理);
而用函數表達式建立的函數是在運行時進行賦值,且要等到表達式賦值完成後才能調用。
這個區別看似微小,但在某些狀況下確實是一個難以發現的陷阱。
出現這個陷阱的本質緣由體如今這兩種類型在Javascript function hoisting(函數提高)
和運行時機(解析時/運行時)上的差別
關於變量提高,可見個人另一篇博文<JavaScript變量提高>上面兩段代碼的函數提高可示意爲下圖:

代碼1段JS函數等同於:spa

function funDeclaration(type){
        return type==="Declaration";
    }
    funDeclaration("Declaration");//=> true

代碼2段JS函數等同於:.net

var funExpression;
    funExpression("Expression");//==>error
    funExpression = function(type){
        return type==="Expression";
    }

上述代碼在運行時,只定義了funExpression變量,但值爲undefined。所以不能在undefined上進行函數調用。此時funExpression賦值語句還沒執行到。爲了進一步加深JS函數兩種類型的區別,下面給出一個更具迷惑性的示例,請看下面的代碼(代碼段4):code

var sayHello;
    console.log(typeof (sayHey));//=>function    
    console.log(typeof (sayHo));//=>undefined
    if (true) {
        function sayHey() {
            console.log("sayHey");
        }
        sayHello = function sayHo() {
            console.log("sayHello");
    }
    } else {
        function sayHey() {
            console.log("sayHey2");
        }
        sayHello = function sayHo() {
            console.log("sayHello2");
        }
    }    
    sayHey();// => sayHey2    
    sayHello();// => sayHello

分析:sayHey是用函數聲明建立的,在JS解析時JS編譯器將函數定義進行了函數提高,也就是說,在解析JS代碼的時候,JS編譯器(條件判斷不造成新的做用域,兩個sayHey函數定義都被提高到條件判斷以外)檢測到做用域內有兩個同名的sayHey定義,第一個定義先被提高,第二個定義接着被提高(第二個定義在第一個定義之下),第二個定義覆蓋了第一個sayHey定義,因此sayHey()輸出sayHey2;而sayHello是用函數表達式建立的,其表達式的內容是在JS運行時(不是解析時)才能肯定(這裏條件判斷就起到做用了),因此sayHello表達式執行了第一個函數定義並賦值,則sayHello()輸出sayHello。htm

  代碼段4的代碼實際上等同於下面的代碼(代碼段5):對象

var sayHello;
    function sayHey() {
            console.log("sayHey");
        }
    function sayHey() {
            console.log("sayHey2");
    }
    console.log(typeof (sayHey));//=>function    
    console.log(typeof (sayHo));//=>undefined
    if (true) {
        //hoisting...
        sayHello = function sayHo() {
            console.log("sayHello");
    }
    } else {
        //hoisting...
        sayHello = function sayHo() {
            console.log("sayHello2");
        }
    }    
    sayHey();// => sayHey2    
    sayHello();// => sayHello

有的人也許會懷疑函數sayHey的定義是第二個覆蓋第一個了麼?咱們能夠把sayHey的源代碼進行輸出,有圖有真相,以下圖所示:

總結

  Javascript 中函數聲明和函數表達式是存在區別的,函數聲明在JS解析時進行函數提高,所以在同一個做用域內,無論函數聲明在哪裏定義,該函數均可以進行調用。而函數表達式的值是在JS運行時肯定,而且在表達式賦值完成後,該函數才能調用。這個微小的區別,可能會致使JS代碼出現意想不到的bug,讓你陷入莫名的陷阱中。

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

  上圖爲sayHello函數執行的主要步驟示意圖。

上圖爲sayHey函數執行主要步驟的示意圖。若對閉包感興趣,能夠看另一篇博文<JavaScript高級特性 閉包>

相關文章
相關標籤/搜索