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

小火柴的藍色理想發表於2016-07-24

前面的話

  一般認為,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);
}

  所以,應該避免在同一作用域中重複宣告

相關文章