1. 程式人生 > >js的作用域與作用域鏈

js的作用域與作用域鏈

性能 使用 plain 賦值 function keyword ack 全局變量 pla

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的作用域與作用域鏈