今天發現了兩個關於函數做用域的神奇例子,這裏和你們分享分享:javascript
var a = 1; function foo() { if (!a) { var a = 2; } alert(a); }; foo();
上面這段代碼在運行時會產生什麼結果?java
咱們來分析一下:es6
1.建立了全局變量 a,定義其值爲 1
2.建立了函數 foo
3.在 foo 的函數體內,if 語句將不會執行,由於 !a 會將變量 a 轉變成布爾的假值,也就是 false
4.跳過條件分支,alert 變量 a,最終的結果應該是輸出 1 閉包
看起來無懈可擊的分析,可是實際上,結果錯誤。答案居然是 2!爲何?函數
什麼叫申明?spa
是指你聲稱某樣東西的存在,好比一個變量或一個函數;但你沒有說明這樣東西究竟是什麼,僅僅是告訴解釋器這樣東西存在而已;code
什麼叫定義?blog
是指你指明瞭某樣東西的具體實現,好比一個變量的值是多少,一個函數的函數體是什麼,確切的表達了這樣東西的意義。ip
因此上面的代碼實際上能夠寫成這樣:作用域
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
這個例子也展現了函數聲明與函數表達式的差異,函數申明會放到做用域的頂部,函數表達式則不會。
最後引用不少書中的一句話:「請始終保持做用域內全部變量的聲明放置在做用域的頂部」,相信如今的你對這句話應該有一個認識了。