【全局】:javascript
【函數】:html
【執行上下文/執行環境】組成:前端
【參考】https://www.jianshu.com/p/76ed896bbf91java
有時也稱環境,執行環境定義了變量或函數有權訪問的其餘數據 ,決定了它們各自的行爲。而每一個執行環境都有一個與之相關的變量對象,環境中定義的全部變量和函數都保存在這個對象中。git
當JavaScript解釋器初始化執行代碼時,它首先默認進入全局執行環境,今後刻開始,函數的每次調用都會建立一個新的執行環境。
當javascript代碼被瀏覽器載入後,默認最早進入的是一個全局執行環境。
當在全局執行環境中調用
執行一個函數時,程序流就進入該被調用函數內,此時JS引擎就會爲該函數建立一個新的執行環境,而且將其壓入到執行環境堆棧的頂部。瀏覽器老是執行當前在堆棧頂部的執行環境,一旦執行完畢,該執行環境就會從堆棧頂部被彈出,而後,進入其下的執行環境執行代碼。這樣,堆棧中的執行環境就會被依次執行而且彈出堆棧,直到回到全局執行環境。github
建立
和執行
兩個階段一、在建立階段,解析器首先會建立一個`變量對象`【variable object】(函數中稱爲`活動對象`【activation object】),它由定義在執行環境中的變量、函數聲明、和參數組成。在這個階段,做用域鏈會被初始化,this的值也會被最終肯定。 二、在執行階段,代碼被解釋執行。 具體過程:每次調用函數,都會建立新的執行上下文。在JavaScript解釋器內部,每次調用執行上下文,分爲兩個階段: 2.1 建立階段【如果函數,當函數被調用,但未執行任何其內部代碼以前】 在進入執行上下文階段,只會將有 var,function修飾的變量或方法添加到變量對象中。 2.1.1 建立做用域鏈(Scope Chain) 2.1.2 建立變量對象(變量,函數和參數) 2.1.3 肯定this的指向 2.2 激活/代碼執行階段: 2.2.1 變量賦值 2.2.2 函數引用, 2.2.3 解釋/執行其餘代碼
函數的全部形參 (若是是函數上下文)
由名稱和對應值組成的一個變量對象的屬性被建立
沒有實參,屬性值設爲 undefined數組
函數聲明
由名稱和對應值(函數對象(function-object))組成一個變量對象的屬性被建立
若是變量對象已經存在相同名稱的屬性,則徹底替換這個屬性瀏覽器
變量聲明
由名稱和對應值(undefined)組成一個變量對象的屬性被建立;
若是變量名稱跟已經聲明的形式參數或函數相同,則變量聲明不會干擾已經存在的這類屬性閉包
能夠將每一個執行上下文抽象爲一個對象並有三個屬性:函數
executionContextObj = { scopeChain: { /* 變量對象(variableObject)+ 全部父執行上下文的變量對象*/ }, variableObject: { /*函數 arguments/參數,內部變量和函數聲明 */ }, this: {} }
一、查找調用函數的代碼。 二、執行函數代碼以前,先建立執行上下文。 三、進入建立階段: 3.1 初始化做用域鏈: 3.2 建立變量對象: 3.2.1 建立arguments對象,檢查上下文,初始化參數名稱和值並建立引用的複製。 3.2.2 掃描上下文的函數聲明: 爲發現的每個函數,在變量對象上建立一個屬性——確切的說是函數的名字——其有一個指向函數在內存中的引用。 若是函數的名字已經存在,引用指針將被重寫。 3.2.3 掃描上下文的變量聲明: 爲發現的每一個變量聲明,在變量對象上建立一個屬性——就是變量的名字,而且將變量的值初始化爲undefined 若是變量的名字已經在變量對象裏存在,將不會進行任何操做並繼續掃描。 3.3 求出上下文內部「this」的值。 四、激活/代碼執行階段: 在當前上下文上運行/解釋函數代碼,並隨着代碼一行行執行指派變量的值。
function foo(i) { var a = 'hello'; var b = function privateB() { }; function c() { } } foo(22);
一、建立階段:foo(22)函數調用時
fooExecutionContext = { scopeChain: { ... }, variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c() a: undefined, b: undefined }, this: { ... } }
二、執行階段:執行流進入函數而且激活/代碼執行階段
fooExecutionContext = { scopeChain: { ... }, variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c() a: 'hello', b: pointer to function privateB() }, this: { ... } }
注意:
解釋:
執行上下文(execution context)屬性:
【注】:
變量對象是與執行上下文相關的數據做用域,存儲了在上下文中定義的變量和函數聲明。
全局上下文中的變量對象就是全局對象
在函數上下文中,咱們用活動對象(activation object, AO)來表示變量對象。
AO & VO 區別與聯繫
活動對象和變量對象實際上是一個東西,只是變量對象是規範上的或者說是引擎實現上的,不可在 JavaScript 環境中訪問,只有到當進入一個執行上下文中,這個執行上下文的變量對象纔會被激活,因此才叫 activation object 吶,而只有被激活的變量對象,也就是活動對象
上的各類屬性才能被訪問。
活動對象是在進入函數上下文時刻被建立的,它經過函數的 arguments 屬性初始化。arguments 屬性值是 Arguments 對象。
AO = VO + function parameters + arguments AO 還包含函數的 parameters,以及 arguments 這個特殊對象
未進入執行階段以前,變量對象(VO)中的屬性都不能訪問!可是進入執行階段以後,變量對象(VO)被激活轉變爲了活動對象(AO),裏面的屬性都能被訪問了,而後開始進行執行階段的操做。
它們其實都是同一個對象,只是處於執行上下文的不一樣生命週期
詞法做用域(lexical scoping)是指,函數在執行時,使用的是它被定義時的做用域,而不是這個函數被調用時的做用域 函數的做用域在函數定義的時候就決定了。 這是由於函數有一個內部屬性 [[scope]],當函數建立的時候,就會保存全部父變量對象到其中,你能夠理解 [[scope]] 就是全部父變量對象的層級鏈,可是注意:[[scope]] 並不表明完整的做用域鏈! 當函數激活時,進入函數上下文,建立 VO/AO 後,就會將活動對象添加到做用鏈的前端。至此,做用域鏈建立完畢。
demo:
function foo() { function bar() { ... } } // 函數建立時,各自的[[scope]]爲: foo.[[scope]] = [ globalContext.VO ]; bar.[[scope]] = [ fooContext.AO, globalContext.VO ];
var scope = "global scope"; function checkscope(){ var scope2 = 'local scope'; return scope2; } checkscope();
執行過程以下:
// 1.checkscope 函數被建立,保存做用域鏈到 內部屬性[[scope]] checkscope.[[scope]] = [ globalContext.VO ]; // 2.執行 checkscope 函數,建立 checkscope 函數執行上下文,checkscope 函數執行上下文被壓入執行上下文棧 ECStack = [ checkscopeContext, globalContext ]; // 3.checkscope 函數並不馬上執行,開始作準備工做,第一步:複製函數[[scope]]屬性建立做用域鏈 checkscopeContext = { Scope: checkscope.[[scope]], } // 4.第二步:用 arguments 建立活動對象,隨後初始化活動對象,加入形參、函數聲明、變量聲明 checkscopeContext = { AO: { arguments: { length: 0 }, scope2: undefined }, Scope: checkscope.[[scope]], } // 5.第三步:將活動對象壓入 checkscope 做用域鏈頂端 checkscopeContext = { AO: { arguments: { length: 0 }, scope2: undefined }, Scope: [AO, [[Scope]]] } // 6.準備工做作完,開始執行函數,隨着函數的執行,修改 AO 的屬性值 checkscopeContext = { AO: { arguments: { length: 0 }, scope2: 'local scope' }, Scope: [AO, [[Scope]]] } // 7.查找到 scope2 的值,返回後函數執行完畢,函數上下文從執行上下文棧中彈出 ECStack = [ globalContext ];
內部函數引用了外部函數的變量,在外部函數上下文被銷燬後,其中的變量仍然能夠被其內部函數引用
由於:
fContext = { Scope: [AO, checkscopeContext.AO, globalContext.VO], }
對的,就是由於這個做用域鏈,f 函數依然能夠讀取到 checkscopeContext.AO 的值,說明當 f 函數引用了 checkscopeContext.AO 中的值的時候,即便 checkscopeContext 被銷燬了,可是 JavaScript 依然會讓 checkscopeContext.AO 活在內存中,f 函數依然能夠經過 f 函數的做用域鏈找到它,正是由於 JavaScript 作到了這一點,從而實現了閉包這個概念。
說明:
我的總結,筆記,有誤請指出,謝謝指教~
ps: 文章格式暫時沒時間處理,以後優化【捂臉】
參考:
一、http://www.cnblogs.com/neusc/p/5771150.html
二、https://yanhaijing.com/javascript/2014/04/29/what-is-the-execution-context-in-javascript/
三、https://github.com/mqyqingfeng/Blog/issues/8
四、https://github.com/mqyqingfeng/Blog/issues/6