你不知道的JavaScript--Item6 var預解析與函數聲明提高(hoist )

一、var 變量預編譯

JavaScript 的語法和 C 、Java、C# 相似,統稱爲 C 類語法。有過 C 或 Java 編程經驗的同窗應該對「先聲明、後使用」的規則很熟悉,若是使用未經聲明的變量或函數,在編譯階段就會報錯。然而,JavaScript 卻可以在變量和函數被聲明以前使用它們。下面咱們就深刻了解一下其中的玄機。javascript

先來看一段代碼:java

(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?markdown

這實際上是 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!。app

再來看一個例子:函數

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 具備這樣的「怪癖」,因此建議你們將變量聲明放在做用域的最上方,這樣就能時刻提醒本身注意了。ui

二、函數聲明「被提早」

前邊說的是變量,接下來咱們說說函數。this

函數的「被提早」還要分兩種狀況,一種是函數聲明,第二種是函數做爲值賦值給變量,也即函數表達式

先說第一種狀況,上代碼:

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 不是函數,固然不能被調用。

總結
經過上面的講解能夠總結以下:

  • 變量的聲明被提早到做用域頂部,賦值保留在原地
  • 函數聲明整個「被提早」
  • 函數表達式時,只有變量「被提早」了,函數沒有「被提早」

三、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嚴格模式下,未聲明的變量(如在前面的代碼片斷中的兩個反面教材)工做時會拋出一個錯誤。

四、單var形式聲明變量

在函數頂部使用單var語句是比較有用的一種形式,其好處在於:

  • 提供了一個單一的地方去尋找功能所須要的全部局部變量
  • 防止變量在定義以前使用的邏輯錯誤
  • 少代碼(類型啊傳值啊單線完成)
    單var形式長得就像下面這個樣子:
function func() {
   var a = 1,
       b = 2,
       sum = a + b,
       myobject = {},
       i,
       j;
   // function body...
}

您可使用一個var語句聲明多個變量,並以逗號分隔。像這種初始化變量同時初始化值的作法是很好的。這樣子能夠防止邏輯錯誤(全部未初始化但聲明的變量的初始值是undefined)和增長代碼的可讀性。在你看到代碼後,你能夠根據初始化的值知道這些變量大體的用途。


系列文章導航:

一、你不知道的JavaScript–Item1 嚴格模式

二、你不知道的JavaScript–Item2 浮點數精度

三、你不知道的JavaScript–Item3 隱式強制轉換

四、你不知道的JavaScript–Item4 基本類型和基本包裝類型(引用類型)

五、你不知道的JavaScript–Item5 全局變量

六、你不知道的JavaScript–Item6 var預解析與函數聲明提高(hoist )

七、你不知道的JavaScript–Item7 函數和(命名)函數表達式

八、你不知道的JavaScript–Item8 函數,方法,構造函數調用

九、你不知道的JavaScript–Item9 call(),apply(),bind()與回調

十、你不知道的JavaScript–Item10 閉包(closure)

十一、你不知道的JavaScript–Item11 arguments對象

十二、你不知道的JavaScript–Item12 undefined 與 null

1三、你不知道的JavaScript–Item13 理解 prototype, getPrototypeOf 和_ proto_

1四、你不知道的JavaScript–Item14 使用prototype的幾點注意事項

1五、你不知道的JavaScript–Item15 prototype原型和原型鏈詳解

1六、你不知道的JavaScript–Item16 for 循環和for…in 循環的那點事兒

1七、你不知道的JavaScript–Item17 循環與prototype最後的幾點小tips

1八、你不知道的JavaScript–Item18 JScript的Bug與內存管理

1九、你不知道的JavaScript–Item19 執行上下文(execution context)

20、你不知道的JavaScript–Item20 做用域與做用域鏈(scope chain)

2一、你不知道的JavaScript–Item21 漂移的this


持續更新中……………….

相關文章
相關標籤/搜索