理解JavaScript如何管理作用域和作用域鏈很重要。因為在作用域鏈中要查找的變量對象的個數直接影響標識符解析的性能。標識符在作用域鏈中的位置越深,查找和訪問它所需的時間越長;如果作用域管理不當,就會給腳本的執行時間帶來負面影響。
當執行JavaScript代碼時,JavaScript引擎會創建一個執行上下文(Execution Context)。執行上下文(有時被稱為作用域)設定了代碼執行時所處的環境。JavaScript引擎會在頁面加載後創建一個全局的執行上下文,然後每執行一個函數時都會創建一個對應的執行上下文,最終建立一個執行上下文的堆棧,當前起作用的執行上下文在堆棧的最頂端。
每個執行上下文都有一個與之關聯的作用域鏈,用於解析標識符。作用域鏈包含一個或多個變量對象,這些對象定義了執行上下文作用域內的標識符。全局執行上下文的作用域鏈中只有一個變量對象,它定義了JavaScript中所有可用的全局變量和函數。當函數被創建(不是執行)時,JavaScript引擎會把創建時執行上下文的作用域鏈賦給函數的內部屬性[[Scope]]。然後,當函數被執行時,JavaScript引擎會創建一個活動對象(Activetion Object),並在初始化時給this、arguments、命名參數和該函數的所有局部變量賦值。活動對象會出現在執行上下文作用域鏈的頂端,緊接其後的是函數[[scope]]屬性中的對象。
在執行代碼時,JavaScript引擎通過搜索執行上下文的作用域鏈來解析諸如變量和函數名這樣的標識符。解析標識符的過程從作用域的頂端開始,按照自上而下的順序進行。
驗證理論function add(num1, num2) {
return num1 + num2;
}
當這段代碼執行開始時,add函數擁有一個僅包含全局變量對象的[[scope]]屬性。如下圖:
在執行add函數時,JavaScript引擎會創建一個新的執行上下文和一個包含this、arguments、num1、num2的活動對象,並把活動對象添加到作用域鏈中。在add()函數內部運行時,JavaScript引擎需要解析函數裡的num1和num2標識符。var total = add(5, 10);
解析過程是從作用域鏈中的第一個對象開始,這個對象就是包含該函數局部變量的活動對象。如果在該對象中沒有找到標識符,就會繼續在作用域鏈中下一個對象裡查找標識符。一旦找到標識符,查找就結束。
高效的數據存取局部變量是JavaScript中讀寫最快的標識符。
一個好的經驗:任何非局部變量在函數中的使用超過一次時,都應該將其存儲為局部變量。
對於數組和對象,始終將那些需要頻繁存取的值存儲到局部變量中。
實際上每次存取HTMLCollection對象屬性,都會對DOM文檔進行動態查詢。
如果需要對HTMLCollection對象的成員反復存取,高效的方式是將它們復制到數組裡。