深入理解javascript作用域系列第三篇——聲明提升(hoisting)

前面的話

一般認爲,javascript代碼在執行時時由上到下一行一行執行的。但是實際上這並不完全正確,主要是因爲聲明提升的存在。

一、變量的聲明提升

a = 2;
var a;
console.log(a);

直覺上,會認爲是undefined,因爲var a 聲明在a = 2;之後,可能變量被重新賦值了,因爲會被賦予默認值undefined。但是,真正的輸出結果卻是2。
就算是下面的代碼這樣,還是會輸出2.

var a = 2;
var a;
console.log(a);

因爲根據編譯器在編譯階段的原理:編譯器查找作用域是否已經有一個名稱爲a的變量存在於同一作用域的集合中。如果是,編譯器會忽略該聲明,繼續進行編譯;否則它會要求作用域在當前作用域集合中聲明一個新的變量,並命名爲a。

鑑於上面這些輸出,可能認爲下面這個代碼片段同樣會輸出2。但是實際上,真正輸出的是undefined。
所有這些和觀感相違背的原因在於編譯器的編譯過程。

console.log( a ) ;
var a  =  2 ;

第一篇介紹過作用域的內部原理,引擎會在解釋javascript代碼之前首先會對其進行編譯。編譯階段中的一部分工作就是找到所有的聲明,並用合適的作用域將它們關聯起來。

包括變量和函數在內的所有聲明都會在任何代碼被執行前首先被處理。

var a = 2;

這個代碼片段實際上包括兩個操作:var a 和 a = 2;
第一個定義聲明是在編譯階段由編譯器進行的。第二個賦值操作會被保留在原地等待引擎在執行階段執行。

//對變量a的聲明提升到最上面後,再執行代碼時,控制檯輸出2
var a;
a = 2 ;
console.log(a);

聲明提升是從它們在代碼中出現的位置被“移動”當前作用域的最上面,這個過程就叫做提升(hoisting)

【注意點】:每個作用域都會進行提升操作。

console.log(a);
var a = 0;
function fn(){
    console.log(b);
    var b = 1;
    function test(){
        console.log(c);
        var c = 2;
    }
    test();
}
fn();
//變量聲明提升後,變成下面這樣
var a ;
console.log(a);
a = 0;
function fn(){
    var b;
    console.log(b);
    b = 1;
    function test(){
        var c ;
        console.log(c);
        c = 2;
    }
    test();
}
fn();

其實真正的提升應該是下面這樣,但爲了便於理解上面的這種表示更能讓人接受。

//變量聲明提升後,變成下面這樣
var a ;
function fn(){
    var b;
    function test(){
        var c ;
        console.log(c);
        c = 2;
    }
    console.log(b);
    b = 1;
    test();
}
console.log(a);
a = 0;
fn();

二、函數聲明提升

聲明包括兩種:變量聲明和函數聲明,不僅變量聲明可以提升,函數聲明也有提升操作。

foo();
function foo(){
    console.log(1);//1
}

上面這個代碼片段之所以能夠在控制檯輸出1,就是因爲foo()函數聲明進行了提升。

function foo(){
    console.log(1);
}
foo();

函數聲明會提升,但函數表達式卻不會提升。

foo();
var foo = function(){
    console.log(1);//TypeError: foo is not a function
}

上面這段程序中的變量標識符foo被提升並分配給全局作用域,因此foo()不會導致ReferenceError。但是foo此時並沒有賦值,foo()由於對undefined值進行函數調用而導致非法操作,因此會拋出TypeError異常。

//變量提升後,代碼如下所示:
var foo;
foo();
foo = function(){
    console.log(1);
}

即使是具名的函數表達式也無法被提升。

foo();//TypeError: foo is not a function
var foo = function bar(){
      console.log(1);
};
//聲明提升後,代碼變爲:
var foo;
foo();//TypeError: foo is not a function
foo = function bar(){
      console.log(1);
};

函數表達式的的名稱只能在函數體內部使用,而不能在函數體外部使用。


var foo = function bar(){
    console.log(1);
};
bar();//TypeError: bar is not a undefined

三、函數覆蓋

函數聲明和變量聲明都會被提升,但是,函數聲明會覆蓋變量聲明。

var a;
function a(){}
console.log(a);//'function a(){}' //函數名代表整個函數

但是,如果變量存在賦值操作,則最終的值爲變量的值。

var a=1;
function a(){}
console.log(a);//1
var a;
function a(){};
console.log(a);//'function a(){}'
a = 1;
console.log(a);//1

【注意點】變量的重複聲明是無用的,但函數的重複聲明會覆蓋前面的聲明(無論是變量還是函數聲明【不是很理解】)
【1】:變量的重複聲明是無用

var a = 1;
var a;
console.log(a);//1

【2】:由於函數聲明提升優於變量聲明提升【覆蓋變量聲明還是提升到變量的前面】,所以變量的聲明無作用。

var a;
function a(){
    console.log(1);
}
a();//1

【3】:後面的函數聲明會覆蓋前面的函數聲明。

a();//2
function a(){
    console.log(1);
}
function a(){
    console.log(2);
}

所以,應該避免在同一作用域中重複聲明。

轉載自:小火柴的藍色理想

http://www.cnblogs.com/xiaohuochai/p/5613593.html
在閱讀老師的博客時,我把它當做看書來對待,所以我的博客大部分來自老師的原話以及實例,再加上自已的一些理解,並且做了適當的標註,還有對老師的代碼進行了一些測試。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章