之前一直覺會認為javascript代碼執行是由上到下一行行執行的。自從看了《你不知道的JS》后發現這個觀點並不完全正確。先來給大家舉一個書本上的的例子:
var a='hello world'; var a; console.log(a);
一開始我覺得輸出的是undefined。但是真正的結果是hello world。帶着疑問再看另外一段代碼:
console.log(a); var a='hello world';
借鑒與上面的例子會認為會輸出一個hello world,或者是拋出一個沒有聲明的異常錯誤,然而發現這兩種想法也是錯誤。輸出的結果是‘undefined’。這書非常人性化的總結出了結論是:
引擎解釋javascript代碼的之前會對其進行編譯。在編譯過程中會查找所有聲明,並用合適作用域將他們關聯起來。換句話說,在代碼執行之前,會對作用域鏈中所有變量和函數聲明先處理完先。所以,當遇到var a='hello world'中是 var a是先在編譯階段執行,然后在執行a='hello world'。所以,第一段代碼實質上是:
var a; a='hello world'; console.log(a);
所以輸出的就就是helloworld。總結一句話就是:只有聲明被提升,而賦值或其他運算會留在原地。所以第二段代碼實際上就是:
var a; console.log(a); a='hello world';
介紹完這兩個經典例子是時候來看看一下這個例子了:
var name = "world"; (function () { if (typeof name == 'undefined') { var name = 'yang'; console.log('Hello ' + name) } else { console.log('Hello ' + name) } })()
根據javascript的運行機制和javascript沒有塊作用域這個特點,可以得出,變量name會聲明提升移至作用域 scope (全局域或者當前函數作用域) 頂部的。所以上述代碼就相當於:
var name = "world"; (function () { var name; if (typeof name == 'undefined') { var name = 'yang'; console.log('Hello ' + name) } else { console.log('Hello ' + name) } })()
因此,if判斷的時候typeof name == 'undefined'是true。所以會執行條件為true里面的代碼。輸出就是Hello yang。
那么如果想實現上面的函數,我們該如何實現?答案非常簡單那就創建塊作用域了。如何最簡單的創建塊作用域呢?那當然是采用es6的新特性let關鍵字。let關鍵字可以將變量綁定到所在的任意區域中通常在{...}中。換句話說。let為其聲明變量隱性劫持到所在區域中。下列例子中:let就綁定到if (typeof name == 'undefined') {...}中
。所以name不會被提升,所以判斷就為假,於是就可以輸出我們期待已久的‘helloworld’。
var name = "world"; (function () { if (typeof name == 'undefined') { let name = 'yang'; console.log('Hello ' + name) } else { console.log('Hello ' + name) } })()
注意點:let所在的塊級作用域,在聲明代碼被運行前,是不會像var那樣會被查找到,提前聲明,而是運行到了該代碼才會被聲明執行。下面例子很好說明這個問題:
(function (){ console.log(b); let b=2; })()