神奇的函式作用域

會飛的Tiger發表於2017-08-12

前言

今天發現了兩個關於函式作用域的神奇例子,這裡和大家分享分享:

第一個例子

    var a = 1;
 
    function foo() {
      if (!a) {
        var a = 2;
      }
      alert(a);
    };
 
    foo();

上面這段程式碼在執行時會產生什麼結果?

 我們來分析一下:

        1.建立了全域性變數 a,定義其值為 1
        2.建立了函式 foo
        3.在 foo 的函式體內,if 語句將不會執行,因為 !a 會將變數 a 轉變成布林的假值,也就是 false
        4.跳過條件分支,alert 變數 a,最終的結果應該是輸出 1 

看起來無懈可擊的分析,但是實際上,結果錯誤。答案竟然是 2!為什麼?

什麼叫申明?

        是指你聲稱某樣東西的存在,比如一個變數或一個函式;但你沒有說明這樣東西到底是什麼,僅僅是告訴直譯器這樣東西存在而已;

什麼叫定義?

        是指你指明瞭某樣東西的具體實現,比如一個變數的值是多少,一個函式的函式體是什麼,確切的表達了這樣東西的意義。

所以上面的程式碼實際上可以寫成這樣:

    var a;
    a = 1;
 
    function foo() {
      var a;    // 關鍵在這裡
      if (!a) {
        a = 2;
      }
      alert(a);   // 此時的 a 並非函式體外的那個全域性變數
    }
    foo();

然後又有人會問,不是有個if嗎?if不成立哪就不會為a賦值為2。

因為 JavaScript 沒有塊級作用域(Block Scoping),只有函式作用域(Function Scoping),所以說不是看見一對花括號 {} 就代表產生了新的作用域,和 C 不一樣!

當解析器讀到 if 語句的時候,它發現此處有一個變數宣告和賦值,於是解析器會將其宣告提升至當前作用域的頂部(這是預設行為,並且無法更改),這個行為就叫做 Hoisting。

怎樣能夠alert出那個a=1?

    let a;
    a = 1;
 
    function foo() {
      let a;    // 關鍵在這裡
      if (!a) {
        a = 2;
      }
      alert(a);   // 此時的 a 並非函式體外的那個全域性變數
    }
    foo();

es6的語法,javascript是有塊級作用域的。

還可以通過閉包的方式實現:

  var a = 1;
     
    function foo() {
      if (!a) {
        (function() {    
          var a = 2;    
        }());        
      };
      alert(a);
    };
     
    foo();

第二個例子

 var a = 1;        
    function test() {
        foo();
                 
        var foo = function() {
            alert(a);
        }
    }
                 
    test();

這個執行的結果是什麼?初略一看,alert(1),但是實際上報錯。

  Uncaught TypeError: foo is not a function。

為什麼會這樣?

  提升的僅僅是變數名 foo,至於它的定義依然停留在原處。因此在執行 foo() 之前,作用域只知道 foo 的命名,不知道它到底是什麼,所以執行會報錯(通常會是:undefined is not a function)。這叫做函式表示式(Function Expression),函式表示式只有命名會被提升,定義的函式體則不會。

 var a = 1;        
    function test() {
        var foo;
        foo();             // 這個時候函式foo,只宣告,未賦值。
        foo = function() {
            alert(a);
        }
    }
                 
    test();

怎麼改?   

 var a = 1;        
    function test() {
        var foo = function() {
            alert(a);
        }
        foo();
    }
                 
    test();        // 1

這個例子也展示了函式宣告與函式表示式的差別,函式申明會放到作用域的頂部,函式表示式則不會。

最後引用很多書中的一句話:“請始終保持作用域內所有變數的宣告放置在作用域的頂部”,相信現在的你對這句話應該有一個認識了。

相關文章