前面的話
一般認為,javascript代碼在執行時是由上到下一行一行執行的。但實際上這并不完全正確,主要是因為聲明提升的存在。本文是深入理解javascript作用域系列第三篇——聲明提升(hoisting)
變量聲明提升
a = 2 ; var a; console.log( a );
直覺上,會認為是undefined,因為var a聲明在a = 2;之后,可能變量被重新賦值了,因為會被賦予默認值undefined。但是,真正的輸出結果是2
console.log( a ) ; var a = 2 ;
鑒于上面的特點,可能會認為這個代碼片段也會同樣輸出2。但,真正的輸出結果是undefined
所有這些和觀感相違背的原因是在于編譯器的編譯過程
第一篇介紹過作用域的內部原理。引擎會在解釋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();
函數聲明提升
聲明包括兩種:變量聲明和函數聲明。不僅變量聲明可以提升,函數聲明也有提升操作
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 bar; var foo = function bar(){ console.log(1); }; bar();//TypeError: bar is not a function
函數覆蓋
函數聲明和變量聲明都會被提升。但是,函數聲明會覆蓋變量聲明
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); }
所以,應該避免在同一作用域中重復聲明
文章列表