2015年11月2日 星期一

Javascript的變量聲明提昇

最近學到了JavaScript的作用域與變量聲明提昇的觀念,
深感與C語言及Java有很大的不同,
如果不是一開始就學JavaScript的人非常可能會把以前的觀念代入而遭遇匪夷所思的錯誤,
所以特地在此做個紀錄。

在JavaScript中,有兩個跟C語言及Java非常不同的特性,

  1. 變量的作用域不為塊級作用域(block-level scope),而且支持函數作用域(function-level scope)。
  2. 在作用域裡會有變量聲明提昇的現象。
下面做個說明:

1.變量的作用域不為塊級作用域(block-level scope),而且支持函數作用域(function-level scope)

在C語言及Java中,只要在括弧( "()" 及 "{}") 中宣告的變數都屬於區域中的區域變數,例如以下程式碼:

int x = 1;

if (true) {

     int x = 2;

     println(x);  //2

}

println(x);       //1

很顯然地應該輸出為 2和1 ,因為大括弧{}中的int x=2;跟外面的int x=1 無關,兩次println(x)的x是不一樣的。
但是在JavaScript中,變數不是使用塊級作用域的,而是函數作用域的,例如以下的程式碼:

var x = 1;

if (true) {

    var x = 2;

    alert(x);    //2

}

alert(x);        //2

結果會是兩次的alert(x)都會顯示2,這是因為在if的大括弧{}裡的var x=2; 的x就是 外層的var x=1;,兩個x並沒有被當成是不同的x,所以內外層的alert(x)的x都是一樣的x。

因為JavaScript支持函數作用域,所以如果用函數來圍往同名變數的宣告的話,就可以區分內外的同名變數,如以下程式碼:

var x = 1;

function myFun(){

     var x = 2;

     alert(x);               //2

}

myFun();

alert(x);                   / /1


就會先顯示2再顯示1,因為此時myFun()裡的x與外層的x是不同的x變數。


2.在作用域裡會有變量聲明提昇的現象

另外一個JavaScript中獨特的特性是變量聲明提昇,以下兩種宣告情形會變提昇至作用域的頂端,即先被執行:
         
            一、以var宣告變數的動作。
            二、函數的無等號宣告 (如: function myFun(){} 這種,其實就等於 var myFun = function(){})

例如以下程式碼:

function sum() {   
    try{
       alert(sum.arguments.length);   //sum為undefined ,出錯
       var sum = 0;
       alert(sum);
    }catch(error){
       alert(error);
    }
}
sum(1,2); 

會補捉到TypeError: Cannot read property 'arguments' of undefined的錯誤,這是因為變量聲明提昇的作用,在提昇之後,其實真正的程式碼執行狀況長得就像下面這樣:

function sum() {   
    try{
       var sum;                                     //變數的宣告被提至作用域頂端
       alert(sum.arguments.length);     //sum為undefined ,出錯
       sum = 0;
       alert(sum);
    }catch(error){
       alert(error);
    }
}
sum(1,2); 

此時就很容易看出Error發生的原因,因為在sum()中,先宣告了一個sum屬性但未賦予其值,是undefined的,所以在alert(sum.arguments.length)中的sum是undefined的,當然就沒有arguments等屬性了。

再看一個例子,現在使用function的非等號式宣告:

var x = 1;
function myFun(){<
     x = 2;
     return;
     function x(){};
}
myFun();
alert(x);

答案是輸出1,因為被變數聲明提昇後的程式碼長得像這樣:

var x = 1;
function myFun(){
     var x;
     x = 2;
     return;
     x = function){};
}
myFun();
alert(x);

所以myFun()跟本沒有影響到外層的var x = 1的x而輸出原來的1了。

參考資料:

  1. JavaScript Scoping and Hoisting
  2. 翻譯 - JavaScript中的作用域與變量聲明提升

沒有留言 :

張貼留言