JavaScript作用域鏈、活動物件
在JavaScript中,函式也是物件,實際上,JavaScript裡一切都是物件。函式物件和其它物件一樣,擁有可以通過程式碼訪問的屬性和一系列僅供JavaScript引擎訪問的內部屬性。其中一個內部屬性是[[Scope]],由ECMA-262標準第三版定義,該內部屬性包含了函式被建立的作用域中物件的集合,這個集合被稱為函式的作用域鏈,它決定了哪些資料能被函式訪問。
當一個函式建立後,它的作用域鏈會被建立此函式的作用域中可訪問的資料物件填充。例如定義下面這樣一個函式:
1 2 3 4 |
function add(num1,num2) { var sum = num1 + num2; return sum; } |
在函式add建立時,它的作用域鏈中會填入一個全域性物件,該全域性物件包含了所有全域性變數,如下圖所示(注意:圖片只例舉了全部變數中的一部分):
函式add的作用域將會在執行時用到。例如執行如下程式碼:
1 |
var total = add(5,10); |
執行此函式時會建立一個稱為“執行期上下文(executioncontext)”的內部物件,執行期上下文定義了函式執行時的環境。每個執行期上下文都有自己的作用域鏈,用於識別符號解析,當執行期上下文被建立時,而它的作用域鏈初始化為當前執行函式的[[Scope]]所包含的物件。
這些值按照它們出現在函式中的順序被複制到執行期上下文的作用域鏈中。它們共同組成了一個新的物件,叫
在函式執行過程中,沒遇到一個變數,都會經歷一次識別符號解析過程以決定從哪裡獲取和儲存資料。該過程從作用域鏈頭部,也就是從活動物件開始搜尋,查詢同名的識別符號,如果找到了就使用這個識別符號對應的變數,如果沒找到繼續搜尋作用域鏈中的下一個物件,如果搜尋完所有物件都未找到,則認為該識別符號未定義。函式執行過程中,每個識別符號都要經歷這樣的搜尋過程。
先來看一段程式碼:
[javascript]
1. name="lwy";
2. function t(){
3. var name="tlwy";
4. function s(){
5. var name="slwy";
6. console.log(name);
7. }
8. function ss(){
9. console.log(name);
10. }
11. s();
12. ss();
13. }
14. t();
當執行s時,將建立函式s的執行環境(呼叫物件),並將該物件置於連結串列開頭,然後將函式t的呼叫物件連結在之後,最後是全域性物件。然後從連結串列開頭尋找變數name,很明顯
name是"slwy"。
但執行ss()時,作用域鏈是: ss()->t()->window,所以name是”tlwy"
下面看一個很容易犯錯的例子:
1. <html>
2. <head>
3. <script type="text/javascript">
4. function buttonInit(){
5. for(var i=1;i<4;i++){
6. var b=document.getElementById("button"+i);
7. b.addEventListener("click",function(){ alert("Button"+i);},false);
8. }
9. }
10. window.onload=buttonInit;
11. </script>
12. </head>
13. <body>
14. <button id="button1">Button1</button>
15. <button id="button2">Button2</button>
16. <button id="button3">Button3</button>
17. </body>
18. </html>
當文件載入完畢,給幾個按鈕註冊點選事件,當我們點選按鈕時,會彈出什麼提示框呢?
很容易犯錯,對是的,三個按鈕都是彈出:"Button4",你答對了嗎?
當註冊事件結束後,i的值為4,當點選按鈕時,事件函式即function(){ alert("Button"+i);}這個匿名函式中沒有i,根據作用域鏈,所以到buttonInit函式中找,此時i的值為4,
所以彈出”button4“。