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预解析与函数声明提升的学习内容,希望对大家的学习有所帮助。