對JavaScript有稍微了解的人都知道,JavaScript中的var是在宣告變數時所使用的陳述式,但很多人對於var的理解都是建立在像是Java這樣的強型別語言的變數宣告上,這樣的理解對於JavaScript的開發者來說是很致命的,這樣的錯誤會產生Bug讓你多加兩天的班,為了避免少了兩個晚上的悠閒,我們現在來看看var究竟在玩什麼把戲。
抬升(Hoisting)
在JavaScript的Hoisting一文有介紹變數的抬升機制,會有這樣的現象是因為編譯器會先掃過所有的程式碼,如果有var
宣告的變數就會將其的定義放入記憶體中,現在再來看幾個比較特別的例子,鞏固觀念。
兩個變數在同行宣告
1 | var a = b, b = 'B'; |
上面的程式在抬升作用下會變得跟下面一樣:
1 | var a, b; |
因為抬升的關係,a
跟b
的宣告已經被抓到Scope的最前面,因此a=b
不會出現Reference Error,而因為b
還未設定初始值,所以a
會是拿到undefined
的值。
迭代中的var
1 | for (var i = 0; i < 10; i++) { |
上面的程式在抬升作用下會變得跟下面一樣:
1 | var i; |
抬升會將i
抬至for
的設置外,這代表著一個令人喪氣的結果: 所有在for
區塊中的i
都共享著相同的變數,所以在for
迴圈後依然可以叫用i
並且其值會是迭代結束的數字10。
for
中的function
上節提到了抬升,我們接續迭代的例子,來看看將一般的程式觀念帶進JS是多麼的危險,請看下面的例子:
1 | var funcs = []; |
這裡我們在迴圈中將return i
的函式加到funcs
的陣列中,並在迭代後各別叫用陣列中的function,並且期望它出現0~9的結果,但卻出乎意料的傳回了10個10。
這是因為它們共享著相同的變數i,這個變數在叫用funcs
陣列中的函式時已經因為迭代結束的關係被變成10了,所以才會得到錯誤的結果。
為了避免共享相同的變數,我們需要在每個函式中建立i的副本,這樣的目的可以用IIFE達到,下面我們用IIFE改寫上面的程式碼:
1 | var funcs = []; |
IIFE
為每個回傳函式建立value
的變數,而這個變數就是每個i
的數值,這樣每個函式就不是共享著變數i的數值,而是每次IIFE中的value,如此一來就可以取得期望的答案了。
覆寫全域物件
當在全域下用var
宣告變數時會蓋掉全域物件(window in browsers)同名的資料,我們看下面的例子:
1 | console.log(window.RegExp); // function RegExp() |
原本的window.RegExp
因為我們宣告了同名的變數而被蓋掉了,在全域中使用var
宣告變數要注意這樣的現象。
未宣告變數(Undeclared Variable)
只有給予變數初始值(ex: a="A"
)稱為未宣告變數(undeclared variable),它跟宣告變數(declared variable)有下面幾點的不同。
- 在嚴格模式下不能使用未宣告變數。
1 |
|
- 不管宣告的在何處,一定是全域變數。
1 | function demo() { |
在之前的JavaScript的Hoisting時有提到抬升只會在相同的作用域下動作,所以var
宣告的a
並不能在全域中被叫用,而未宣告變數可以。
- 宣告變數可以在未賦值前叫用,而未宣告變數會拋出錯誤。
1 | console.log(c); // ReferenceError: c is not defined |
未宣告變數在沒有賦值前都是不存在的。
- 宣告變數不是configurable,而未宣告變數是configurable
在Proerty Descriptor中的configurable
是決定可不可以修改Property Decriptor
及可不可以delete
的設定值,true
的話就是可以做上述的操作,否則就不行。
1 | var e = 'E'; |
上面的例子有一個宣告變數e及未宣告變數f,對它們做delete
,我們可以看到e
因為configurable
是false
所以不能刪除,還是維持原來的值E
,可是f
已經被刪除了。
實際上在嚴格模式下,刪除宣告變數會拋出Uncaught SyntaxError。
結語
總結一下var的特別之處:
- 宣告會被抬升至Scope的頂端。
- 在迴圈中叫用函式時會因共享相同變數,而在函式中產生非預期結果,使用IIFE可以避免這樣的問題。
- 在全域下宣告變數會覆蓋
window
的變數。
未宣告變數有下面幾個特性:
- 嚴格模式下未宣告變數會拋例外
- 未宣告變數在任何Scope下都會是全域變數
- 賦值時才會建立
- 是
configurable