JS 作用域鏈 學習記錄

看完冴羽大大寫的作用域鏈分析的文章腦子還是有點迷糊,在此再針對其中的 “函數執行上下文中作用域鏈和變量對象的創建過程” 幫助自己重新做一份梳理和學習記錄。

1. 執行上下文

  當JS代碼執行到可執行函數時,即運行到執行函數的地方,會創建其對應的執行上下文
  每個執行上下文都有三個非常重要的屬性,即:

  • 變量對象(VO) (函數執行時的活動對象: AO)
  • 作用域鏈(Scope Chain)
  • this

2. 什麼是作用域鏈?

    JS在查找變量時,會先從當前執行上下文的變量對象中查找,如果沒找到,則從父級執行上下文的變量對象中查找,依次向上遞推,一直找到全局上下文的變量對象爲止。這樣由多個執行上下文的變量對象構成的鏈表就叫做作用域鏈

3. JS中作用域鏈的創建和變化過程

JS中作用域鏈的創建和變化分爲兩個時期: 函數創建時期函數激活時期

  • 3.1 函數創建時期

        由於JS採用的是靜態作用域(詞法作用域),所以函數的作用域在函數定義時就已經決定了,而與函數調用位置無關。
        導致上述現象的原因與JS函數的一個內置屬性 [[scope]] 有關。每一個函數創建後(定義後),其內部的[[scope]] 都會將該函數所有的父變量對象(VO)保存到其中。形成父變量對象的一個層級鏈。但是,這並不是完整的作用域鏈 !

    function foo(){
    	function bar(){
    	
    	}
    }
    
    這兩個函數創建時,其內部各自的[[scope]]是這樣的,[[scope]]中保存各自所有的父變量對象:
    
    foo.[[scope]] = [
    	globalContext.VO
    ]
    
    bar.[[scope]] = [
    	fooContext.AO,
    	globalContext.VO
    ]
    
  • 3.2 函數激活時期

        函數激活,即開始執行函數的時候,會進入函數上下文,創建該函數的變量對象(VO/AO)之後,將該變量對象添加到作用域鏈的前端。形成最終完整的作用域鏈。
        這時執行上下文完成的作用域鏈我們稱爲Scope:

    Scope = [AO].concat([[Scope]])   // 數組拼接
    

4.通過一個小Demo分析完整作用域鏈的形成過程。

var scope = "globalScope";
function checkScope(){
	var scope2 = "localScope";
	return scope2;
}

checkScope();

分析執行過程:

  1. checkScope函數被定義的時候即 創建checkScope函數,
    checkScope內置的[[scope]]屬性會保存其所有父級VO,這裏就是全局執行上下文的VO

    checkScope.[[scope]] = [
    	globalContext.VO
    ]
    
  2. 調用checkScope函數的時候,會形成其對應的執行上下文,checkScope函數被壓入執行上下文棧(如果checkScope函數裏面還調用了其他函數,那些函數也會被依次壓入執行上下文棧):

    ECStack = [
    	(push)  checkScopeContext, 
    	globalContext
    ]
    
  3. 此時checkScope函數不會立即從棧中pop出來執行。 而是會先進行一些**“準備工作”**:

    第一步: 複製checkScope函數創建時的父級VO作用域鏈,作爲對象保存到其執行上下文對象中:

    // check
    checkScopeContext = {
    	Scope: checkScope.[[scope]]
    }
    

    第二步: 生成checkScope自己的變量對象AO: 用arguments創建活動對象,隨後初始化活動對象,加入形參、函數聲明以及變量聲明:

    AO: {
    	arguments: [
    		length: 0
    	],
    	scope: undefined
    }  // 這是新創建的AO
    

    第三步: 將新創建的AO壓入原來的作用域頂端形成完整的作用域鏈:

    checkscopeContext = {
    	AO: {
            arguments: {
                length: 0
            },
            scope2: undefined
        },  // checkScope的活動變量
        
        Scope: [AO, [[Scope]]]  // 完整的作用域鏈
    }
    
  4. 準備工作完成後,開始執行函數。首先根絕代碼執行順序依次修改AO中的屬性值。

    checkscopeContext = {
        AO: {
            arguments: {
                length: 0
            },
            scope2: 'local scope'
        },
        Scope: [AO, [[Scope]]]
    }
    
  5. 執行函數時將checkScopeContext從上下文執行棧ECStack中彈出。函數執行完畢。

    ECStack = [
    	(pop) checkScopeContext,
    	globalContext
    ]
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章