1、var 變量預編譯
JavaScript 的語法和 C 、Java、C# 類似,統稱為 C 類語法。有過 C 或 Java 編程經驗的同學應該對“先聲明、後使用”的規則很熟悉,如果使用未經聲明的變量或函數,在編譯階段就會報錯。然而,JavaScript 卻能夠在變量和函數被聲明之前使用它們。下面我們就深入了解一下其中的玄機。
先來看一段代碼:
(function() { console.log(noSuchVariable);//ReferenceError: noSuchVariable is not defined })();
運行上面代碼立馬就報錯,不過,這也正是我們期望的,因為 noSuchVariable 變量根本就沒有定義過嘛!再來看看下面的代碼:
(function() { console.log(declaredLater); //undefined var declaredLater = "Now it's defined!"; console.log(declaredLater);// "Now it's defined!" })();
首先,上面這段代碼是正確的,沒有任何問題。但是,為什麼不報錯了?declaredLater 變量是在調用語句後面定義的啊?為什麼居然輸出的是 undefined?
這其實是 JavaScript 解析器搞的鬼,解析器將當前作用域內聲明的所有變量和函數都會放到作用域的開始處,但是,只有變量的聲明被提前到作用域的開始處了,而賦值操作被保留在原處。上述代碼對於解析器來說其實是如下這個樣子滴:
(function() { var declaredLater; //聲明被提前到作用域開始處了! console.log(declaredLater); // undefined declaredLater = "Now it's defined!"; //賦值操作還在原地! console.log(declaredLater);//"Now it's defined!" })();
這就是為什麼上述代碼不報異常的原因!變量和函數經過“被提前”之後,declaredLater 變量其實就被放在了調用函數的前面,根據 JavaScript 語法的定義,已聲明而未被賦值的變量會被自動賦值為 undefined ,所以,第一次打印 declaredLater 變量的值就是 undefined,後面我們對 declaredLater 變量進行了賦值操作,所以,第二次再打印變量就會輸出Now it's defined!。
再來看一個例子:
var name = "Baggins"; (function () { console.log("Original name was " + name);// "Original name was undefined" var name = "Underhill"; console.log("New name is " + name);// "New name is Underhill" })();
上述代碼中,我們先聲明了一個變量 name ,我們的本意是希望在第一次打印 name 變量時能夠輸出全局范圍內定義的 name 變量,然後再在函數中定義一個局部 name 變量覆蓋全局變量,最後輸出局部變量的值。可是第一次輸出的結果和我們的預期完全不一致,原因就是我們定義的局部變量在其作用域內被“提前”了,也就是變成了如下形式:
var name = "Baggins"; (function () { var name; //注意:name 變量被提前了! console.log("Original name was " + name);// "Original name was undefined" name = "Underhill"; console.log("New name is " + name);//"New name is Underhill" })();
由於 JavaScript 具有這樣的“怪癖”,所以建議大家將變量聲明放在作用域的最上方,這樣就能時刻提醒自己注意了。
2、函數聲明“被提前”
前邊說的是變量,接下來我們說說函數。
函數的“被提前”還要分兩種情況,一種是函數聲明,第二種是函數作為值賦值給變量,也即函數表達式。
先說第一種情況,上代碼:
isItHoisted();//"Yes!" function isItHoisted() { console.log("Yes!"); }
如上所示,JavaScript 解釋器允許你在函數聲明之前使用,也就是說,函數聲明並不僅僅是函數名“被提前”了,整個函數的定義也“被提前”了!所以上述代碼能夠正確執行。
再來看第二種情況:函數表達式形式。還是先上代碼:
definitionHoisted();// "Definition hoisted!" definitionNotHoisted();// TypeError: undefined is not a function function definitionHoisted() { console.log("Definition hoisted!"); } var definitionNotHoisted = function () { console.log("Definition not hoisted!"); };
我們做了一個對比,definitionHoisted 函數被妥妥的執行了,符合第一種類型;definitionNotHoisted 變量“被提前”了,但是他的賦值(也就是函數)並沒有被提前,從這一點上來說,和前面我們所講的變量“被提前”是完全一致的,並且,由於“被提前”的變量的默認值是 undefined ,所以報的錯誤屬於“類型不匹配”,因為 undefined 不是函數,當然不能被調用。
總結
通過上面的講解可以總結如下:
變量的聲明被提前到作用域頂部,賦值保留在原地
函數聲明整個“被提前”
函數表達式時,只有變量“被提前”了,函數沒有“被提前”
3、var的副作用
隱式全局變量和明確定義的全局變量間有些小的差異,就是通過delete操作符讓變量未定義的能力。
通過var創建的全局變量(任何函數之外的程序中創建)是不能被刪除的。
無var創建的隱式全局變量(無視是否在函數中創建)是能被刪除的。
這表明,在技術上,隱式全局變量並不是真正的全局變量,但它們是全局對象的屬性。屬性是可以通過delete操作符刪除的,而變量是不能的:
// 定義三個全局變量 var global_var = 1; global_novar = 2; // 反面教材 (function () { global_fromfunc = 3; // 反面教材 }()); // 試圖刪除 delete global_var; // false delete global_novar; // true delete global_fromfunc; // true // 測試該刪除 typeof global_var; // "number" typeof global_novar; // "undefined" typeof global_fromfunc; // "undefined"
在ES5嚴格模式下,未聲明的變量(如在前面的代碼片段中的兩個反面教材)工作時會拋出一個錯誤。
4、單var形式聲明變量
在函數頂部使用單var語句是比較有用的一種形式,其好處在於:
提供了一個單一的地方去尋找功能所需要的所有局部變量
防止變量在定義之前使用的邏輯錯誤
少代碼(類型啊傳值啊單線完成)
單var形式長得就像下面這個樣子:
function func() { var a = 1, b = 2, sum = a + b, myobject = {}, i, j; // function body... }
您可以使用一個var語句聲明多個變量,並以逗號分隔。像這種初始化變量同時初始化值的做法是很好的。這樣子可以防止邏輯錯誤(所有未初始化但聲明的變量的初始值是undefined)和增加代碼的可讀性。在你看到代碼後,你可以根據初始化的值知道這些變量大致的用途。
以上就是針對javascript的var預解析與函數聲明提升的學習內容,希望對大家的學習有所幫助。