《高效能JavaScript》讀書總結——作用域鏈

weixin_33785972發表於2017-08-23

資料存取

JS中有如下四種基本資料的存取:

  • 字面量:字串、數字、布林值、物件、陣列、函式、正規表示式、null和undefined。
  • 本地變數:var/let 定義的資料儲存單元。
  • 陣列元素
  • 物件成員
    通常情況下,訪問速度排序:字面量 > 本地變數 > 陣列元素 > 物件成員。個別瀏覽器的版本,可能有細微差別。

作用域

執行環境/執行期上下文(execution context): 是指當前變數或函式有權訪問的其它資料。每個執行環境都有一個與之關聯的變數物件(variable object, VO),VO是不能直接訪問的,執行環境中定義的所有變數和函式都會儲存在這個物件中,解析器在處理資料的時候就會訪問這個內部物件。
全域性執行環境是最外層的一個執行環境,在web瀏覽器中全域性執行環境是window物件,因此所有全域性變數和函式都是作為window物件的屬性和成員函式建立的。每個函式都有自己的執行環境,當執行流進入一個函式的時候,函式的環境會被推入一個函式棧中,而在函式執行完畢後執行環境出棧並被銷燬,儲存在其中的所有變數和函式定義隨之銷燬,控制權返回到之前的執行環境中,全域性的執行環境在應用程式退出(瀏覽器關閉)才會被銷燬。
每一個JS函式可以看做是Function物件的一個例項,並且含有一個內部屬性[[Scopes]],[[Scopes]]包含了一個函式被建立的作用域中物件的集合。這個集合被稱為作用域鏈,它決定哪些資料能被函式訪問。函式作用域中的每個物件被稱為一個可變物件,每個可變物件都是以“key-value”形式存在。
典型的作用域鏈:

  1. 函式建立時
    此時函式的作用域鏈會壓入第一個作用域物件,即建立此函式的作用域中可訪問的資料物件填充。如下圖所示:
4944427-48ee5069cc4bba9e.png
scope1.png

注意:這個作用域物件是可變的,可以理解為這個物件是引用的。
具體物件資訊可以在chrome dev tool中檢視,如下圖:

4944427-2f9ebf872328209d.png
scope1-tool.png
  1. 函式執行時
    每次執行函式時都會建立一個執行環境,每個執行環境都是獨一無二的,多次呼叫函式就會導致建立多個執行環境。此時會將會將一個被稱為“活動物件”(activation object,AO)的新物件作為第二個作用域物件壓入作用域鏈。如下圖所示:
4944427-9709f477eae42387.png
scope2.png

在函式的執行過程中,每遇到一個變數或者函式,都會在作用域鏈中按照順序進行查詢,直到遍歷所有的作用域,此過程會影響執行效能。

  1. 閉包時的作用域
    如下一段程式碼中,包含了閉包
functionassignEvents(){
    var id= "xdi9592";
    document.getElementById("save-btn").onclick =function(event){
        saveDocument(id);
    };
}   
4944427-98c7ce04672a331c.png
scope3.png
4944427-b5a1093ace42448b.png
scope3-tool.png
  1. 其他改變作用域的情況
    一般作用域鏈的順序是按照呼叫的順序排列的,但是特殊情況下會改變。
  • with
    執行with語句時,會將with帶入的物件壓入作用域鏈,導致呼叫深度發生變化。
functioninitUI(){
    with(document){
        var bd= body,
        links=getElementsByTagName_r("a"),
        i=0,
        len= links.length;
        while(i<len){
        update(links[i++]);
        }
        getElementById("go-btn").onclick=function(){
        start();
        };
        bd.className ="active";
    }
}
4944427-71b9617f98d995b9.png
with.png
  • try-catch
    類似with,當try塊發生異常時,程式跳轉到catch子句,並且把異常物件壓入作用域首位。catch子句執行完作用域鏈恢復之前的狀態。由於加深了呼叫深度,如果在catch子句執行操作會造成效能問題。可以採用錯誤處理函式的方式,改變作用域鏈的狀態,從而減少呼叫深度。
try{
    methodThatMightCauseAnError();
}catch (ex){
    handleError(ex);//delegate tohandlermethod
}

識別符號解析的效能

在執行環境的作用域鏈中,一個識別符號的位置越深,他的讀寫速度就越慢,因此函式中讀寫區域性變數是最快的,讀寫全域性變數通常是最慢的。
改進辦法,通過賦值給區域性變數,改變識別符號的深度,從而提高讀寫速度。

for(var i = 0; i < document.getElementsByTagName("a").length; i++){
    document.getElementsByTagName("a")[i].class = 'active'
}
//改進後
var list = document.getElementsByTagName("a");
for(var i = 0; i < list.length; i++){
    list[i].class = 'active'
}

相關文章