1. 程式人生 > >javascript精雕細琢(三):作用域與作用域鏈

javascript精雕細琢(三):作用域與作用域鏈

目錄

引言

       作用域與作用域鏈是JS應用中無時無刻不在影響程式執行的關鍵屬性,但是由於它的不可見性,或者說它存在的過於普遍,簡直就像空氣一樣。所以對它的談及,都很簡單,而理解起來也不復雜。        但是由於它的重要性,對它做一個篇幅的說明,也是一件理所應當的事情。而且它其實也並沒有理解上那麼簡單。        本文對作用域及作用域鏈的說明,可能並不全面。筆者儘量以自身開發過程中的理解,做到表述全面。

1、執行環境

       按照《JavaScript高階程式設計》第3版中的定義——執行環境定義了變數或函式有權訪問其他資料,決定了它們各自的行為。每個執行環境都有一個與之關聯的變數物件

,環境中定義的所有變數和函式都儲存在這個物件中。

       對於環境的簡單理解,可以理解為{}一個大括號就是一個執行環境。如{{}},就是包含關係的兩個執行環境。而由於所有的函式和變數,最終都是通過最外圍有對應的呼叫,才能最終實現執行,所以將最外圍的執行環境定義為全域性執行環境,在宿主為web瀏覽器時,全域性執行環境就為window物件

       每個函式都有自己的執行環境。當執行程式進入一個函式時,函式的環境就會被推入一個環境棧中。而在函式執行之後,棧將環境彈出,把控制權返回給之前的執行環境。之後,該環境因為執行完畢,隨即被銷燬。但有一個例外,全域性環境將永遠不會被銷燬,直到程式被關閉,如關閉瀏覽器。

由上圖我們清晰的看到JavaScript的兩個階段的執行流程。

2、作用域與作用域鏈

       作用域鏈和作用域是一個包含關係。當代碼在一個環境中執行時,會建立變數物件的一個作用域鏈。而作用域鏈中,包含的就是一個個有序排列的作用域。作用域鏈的作用,就是保證執行環境中的變數和函式的有序訪問。        每次的變數或函式宣告,都會形成一個識別符號(即變數名或函式名)。 作用域鏈中的向上查詢,實際上就是識別符號的查詢。找到匹配的識別符號,就呼叫它的值。        作用域鏈中包含的每個作用域物件,其中都包含著它自身環境中可訪問的變數或函式(script作用域中,為全部的變數和函式;函式作用域中,子函式呼叫父函式環境中的變數或函式會形成閉包,那麼可訪問變數就為閉包呼叫的變數)。換一種角度理解的話,這些環境中可訪問的變數或函式就是識別符號,是當前作用域中可通過作用域鏈向上查詢的識別符號。

       一大段的文字不光讀起來枯燥,理解起來也困難。所以,stop talking,show code。

以函式為例

function test() {
    let a = 1; //註釋此行,保留其他a,c()列印2,並且作用域鏈失去閉包物件
    let b = 0;
    
    return () => { //形成閉包環境
        console.log(a);
        }
    }

    let a = 2; //註釋此行及test中的a,c()列印3
    window.a = 3;  //註釋全部a,報錯

    const c = test();
    c();

    console.dir(test);
    console.dir(c);
        

以上程式碼,通過3次註釋以及chrome瀏覽器列印,可以清晰的看到作用域與作用域鏈的形成及作用域鏈*按順序查詢**的特性

1)首先是閉包呼叫——所有a都不註釋 由上圖可以看出,此時作用域鏈中存在3個作用域物件。 按索引順序為0—Closure閉包環境、1—Script標籤環境、2—Global全域性環境; 索引順序就是識別符號的查詢順序,所以當執行函式c()時,先在Closure閉包環境中查詢,然後是Script標籤作用域,最後是Global全域性作用域。因為形成了閉包,那麼識別符號的查詢肯定能在閉包環境中找到對應結果,所以列印1; 值得注意的一點是,Closure與Script作用域物件中,儲存的識別符號的不同。closure只儲存呼叫值a = 1,並沒有儲存b = 0。而Script中將環境中的所有識別符號都存在了物件中;

2)然後是取消閉包呼叫,改為Scriptbiaoqian作用域查詢——註釋test中的a = 1 由上圖可以看出,此時作用域鏈中的Closure閉包作用域消失,只剩下2個物件0—Script標籤環境、2—Global全域性環境; 那麼同樣按索引順序查詢,找到了Script標籤環境中的a = 2,沒有繼續尋找Global全域性環境中的window.a = 3,最終列印2;

3)最後是Global全域性作用域查詢——註釋test中的a = 1及script中的a = 2 與2)中情況對比,作用域鏈中的物件沒有改變。但是Script物件中的識別符號,隨著a = 2被註釋而消失 按索引順序查詢,找到了Script標籤環境中沒有a,繼續尋找Global全域性環境,最終找到window.a = 3,列印3; 函式test的列印,從始至終都是Script標籤作用域及Global全域性作用域。理解了c函式的作用域鏈,自然就明白了函式test的,所以不再展開贅述。

如有錯誤或闡述不充分之處,歡迎指正~