js的作用域與作用域鏈
JavaScript的作用域和作用域鏈。在初學JavaScript時,覺得它就和其他語言沒啥區別,尤其是作用域這塊,想當然的以為“全局變量就是在整個程序的任何地方都可以訪問,也就是寫在函數外的變量,局部變量也就是寫在函數內部或循環體內部,出了循環體和函數就不可訪問”,但是在JavaScript中並不是這麽簡單,需要去深入的學習。
一. 什麽是作用域
任何程序語言都有作用域的概念,簡單的說,作用域就是變量與函數的可訪問範圍。比如C/C++等,都是塊級作用域,也就是說在每一個代碼塊內聲明的變量,出了這個代碼塊就是不可見的,而JS根本就沒有塊級作用域這個概念,而是函數作用域。如下面的例子:
1 2 3 4 5 6 7 8 9 10 |
function test(){
var sum = 0;
for (var i = 0; i < 2; i++)
{
sum = sum + i;
}
console.log(i);
}
test();
|
上面的程序輸出結果為2,知道c和c++或java的同學都會知道,在for循環體塊中定義的變量,在循環體外部是不可訪問的,可是在JS中,沒有塊作用域這個概念,只有函數作用域的概念,所以只要是在函數體內部定義的變量,在這個函數體內都是可訪問的。
二. 函數作用域
看到上面的小例子,應該對函數作用域有一點模糊的理解吧。其實也就是說,變量和函數在聲明它們的函數體中,和這個函數體嵌套的任意函數體內部都是可訪問的。下面再看個小例子:
1 2 3 4 5 6 7 8 9 10 11 |
function test(){
var name = "xiyangyang" ;
function showname(){
console.log( name );
}
showname();
}
test(); //xiayangyang
showname(); //ReferenceError: showname is not defined
|
結果如代碼後的註釋,可見變量和函數只在當前運行函數體的內部有效,在當前運行函數體的外部是無效的。
三. 變量作用域
JS的變量計較特殊,下面是一些小例子,請看它的特殊點:
(1)全局變量被覆蓋:
1 2 3 4 5 6 7 8 9 |
var name = "huitaiyang"
function test(){
console.log( name );
var name = "xiyangyang" ;
console.log( name );
}
test();
|
如上代碼不知道JS變量作用域的肯定會覺得,第一個輸出“huitaiyang”,第二個輸出“xiyangyang”,其實不是的,第一個會輸出“undefined”,第二個是“xiyangyang”(其實一開始我也是這麽想的),無論如何請隨時謹記,JS是函數作用域,也就是它首先會在函數內部尋找name屬性,找不到才會繼續往上一層尋找。
這個小例子中,在test函數內部有name的定義,也就是找到了,它不會在往上層尋找了,但是name在打印時並沒有賦值,所以就輸出了undefined,第二次打印時已經賦值,就是正常的“xiyangyang”。
(2)沒有var聲明的局部變量,上升為全局變量:
1 2 3 4 5 6 7 |
function test(){
name = "xiyangyang" ;
console.log( name );
}
test();
console.log( name );
|
上面代碼兩次打印結果都是“xiyangyang”,所以沒有var聲明的變量都是全局變量,是window對象的屬性,console.log(name); 這樣和上面的結果一致。
四. 作用域鏈
一旦函數創建,函數的作用域就確定了,作用域鏈就由作用域中對象的集合組成。當函數執行時,它會把當前正在執行的函數內部的所有變量(包括this)置於作用域鏈的首部,會把該函數外部的對象置於第二,第三…層,window對象置於最外層。作用域鏈的層數和函數的層數有關。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
var name = "huitaiyang"
function test(){
var name = "xiyangyang" ;
function show1(){
var name = "lanyangyang" ;
console.log( name );
}
function show2(){
console.log( name );
}
show1();
show2();
}
test();
|
在上面的例子中,打印出來的結果是“langyangyang”和“xiyangyang”。
解析:當test()執行時,運行到show1,創建show1的執行環境,將show1的所有內部變量都置於作用域鏈的首部,然後將函數test的所有對象都置於show1的後面,最後是window對象,然後從作用域鏈的頭部開始查找name屬性,所以show1()的作用域鏈是:show1()->test()->window
,所以name就是lanyangyang;同理show2()的作用域鏈是:show2()->test()->window
,所以name就是xiyangyang。
五. 作用域鏈和代碼優化
看完上面的所有內容的同學就會很容易理解這裏,如下這個小例子:
1 2 3 4 5 |
function changeColor(){
document.getElementById( ‘btn‘ ).onclick = function (){
document.getElementById( ‘obj‘ ).style.backgroundColor = ‘red‘ ;
};
}
|
大家都知道document是全局變量,也就是在作用域鏈的最尾部,查找是很消耗資源的,所以需要優化,優化後代碼為:
1 2 3 4 5 6 |
function changeColor(){
var doc = document;
doc.getElementById( ‘btn‘ ).onclick = function (){
doc.getElementById( ‘obj‘ ).style.backgroundColor = ‘red‘ ;
};
}
|
這樣找一次就夠了,所以當一個對象被跨作用域訪問時,可以把它存儲為局部變量使用,這樣就起到了優化的作用。
六. 修改作用域鏈
這裏就簡單的提一下,能理解就有好了,with和catch會修改函數的作用域鏈。
(1)如果代碼塊中有with,則with中的所有對象會置於當前作用域鏈的最頂層,當前函數會被置於第二層。會降低代碼的性能,所以不推薦使用。
(2)catch語句會把異常對象置於作用域鏈的頂部,當前執行函數會被置於第二層。同樣影響代碼的性能。
js的作用域與作用域鏈