JS 作用域與變數提升---JS 學習筆記(三)
你知道下面的JavaScript程式碼執行時會輸出什麼嗎?
var foo = 1; function bar() { if (!foo) { var foo = 10; } console.log(foo); } bar();
答案是“10”,吃驚嗎?那麼下面的可能會真的讓你大吃一驚:
var a = 1; function b() { a = 10; return; function a() {} } b(); console.log(a);
答案是 “1”。為什麼會這樣呢,這就涉及到 JS 裡面的作用域、作用域鏈和提升的相關知識了。
首先要明白 作用域和作用域鏈的內容 (點選閱讀),現在主要介紹提升。
在 JavaScript 中,當遇到 var a = 1; 這個語句時,我們可能會這麼認為,“為一個變數分配記憶體,給這個變數命名為 a,再把 1 儲存進去”。然而這個過程並不完全正確。當編譯器遇到 var a = 1 時,會做以下兩個步驟:
1. 遇到 var a ,首先會先看看 當前的作用域 中是否已經有 a,若有,就不再宣告 a,若沒有,就會在當前的作用域,建立一個 a
2. 然後處理 a = 1 。先檢視當前作用域,是否有 a 。如果有,就把 1 賦值給 a 。如果沒有,就向上一個作用域中尋找 a,直到尋找到全域性作用域。全域性作用域中如果還沒有,就會丟擲異常。
所以,當我們看到 var a = 1; 會認為這是一個宣告,實際上這是兩個宣告, var a 和 a = 1 。第一個宣告會在編譯階段進行,第二個宣告會在原地等待執行。函式宣告和變數宣告總會被 JavaScript 的編譯器 提升 到他們 所在作用域的頂部。 有了這些知識就可以解釋上面的程式碼了
第一段程式碼實際上如下圖:在遇到並執行 bar()函式時,執行到 if 語句裡面的 foo,此時會先查詢 bar()函式的作用域內有沒有宣告 foo:
若沒有宣告,就像上一個作用域中尋找 foo 有沒有被宣告。
若聲明瞭,就直接將宣告提升到 bar 作用域裡的頂部(foo = 10 留在原地按順序等待執行),這樣,foo 就是 undefined,!foo 就是 true,可以進入 if 迴圈。
第二段程式碼實際上如下圖:執行 b()時,首先就是 a = 10;這條語句,執行這條語句之前,先看 a 在 b 中是否被宣告,若沒有被宣告就向上一個作用域中尋找 a ,現在的情況是,在 b 函式內部,a 以函式宣告的方式,被聲明瞭,那麼就要把這個 a 函式宣告提升到 a 所在的作用域的頂部,再按順序執行後面的程式碼。
對於提升總結的知識點:
1. 函式宣告和變數宣告都會提升,但在同一個作用域相同名字的函式宣告優先於變數宣告。
2. 變數宣告會提升,但變數賦值的過程不會提升,會在原地等待被執行。
3. 提升後,賦值語句會向前覆蓋提升的內容。例子 2 中,b 函式的內部,最開始 a 是函式提升,a 的本質是函式,但是函式提升後,執行了一個賦值語句:a = 10;此時,a 就是一個 number 型別的變量了。
對於這種情況,我們在寫 JavasScript 程式碼的時候該怎麼做呢?
使用單 var 模式:避免變數提升所帶來的問題
/*jslint onevar: true [...] */ function foo(a, b, c) { var x = 1, bar, baz = "something"; }
本文參考以下資料:
顏海鏡的部落格: JavaScript作用域鏈和提升機制
你不知道的JavaScript
JavaScript 高階程式設計