在以前的 執行上下文 中已知 JavaScript 代碼執行一段可執行代碼時,會建立對應的 執行上線文(execution context)前端
在建立執行上下文時,隨之建立的三個屬性:bash
- 變量對象(Variable Object, Vo)
- 做用域鏈(Scope chain)
- this
結合 變量對象 來講,上下文中查找變量的時候,會先從當前上下文的變量對象中查找,若是沒有找到,就會從父級(詞法層面的父級)執行上下文的變量對象中查找,一直找到全局上下文的變量對象,也就是全局對象。這樣有多個執行上下文的變量對象構成的鏈表,就叫作做用域鏈。函數
下面,從一個函數的建立和激活兩個時期講解做用域鏈是如何建立和變化的:ui
已知 JavaScript 中的函數做用域採用的是詞法做用域,在函數定義的時候就決定了的。this
這是由於函數有一個內部屬性
[[scope]]
, 當函數建立的時候,就會保存全部父變量對象到其中,能夠理解[[scope]]
就是全部父變量對象的層級鏈,可是[[scope]]
並不表明完整的做用域鏈!spa
以下:
// 函數聲明,一個函數被建立
function foo() {
function bar() {
}
}
// 此時 foo 未被調用,上下文中的代碼未執行,bar 未被聲明
// foo 和 bar 的 [[scope]] 爲:
foo.[[scope]] = [
globalContext.VO // VO 變量對象,這裏是全局的變量對象 window
];
bar.[[scope]] = [
fooContext.AO, // bar 將在 foo 被調用時聲明,[[scope]] 將包含 foo 的活動對象
globalContext.VO
];
複製代碼
當函數激活時,進入函數上下文,建立 AO/VO 後,就會將活動對象添加到做用域鏈的前端。 這時候執行上下文的做用域,咱們命名爲 Scope:
Scope = [AO].concat([[Scope]]);
code
至此,做用域鏈建立完畢對象
var scope = "global scope";
function checkscope() {
var scope2 = 'local scope';
return scope2;
}
checkscope();
複製代碼
執行過程以下:ip
- 執行流進入程序,進入全局上下文,建立變量對象,將 上下文中的 變量函數聲明添加到變量對象中
- 函數 checkscope 被聲明建立的同時,保存詞法父級做用域鏈
globalContext.VO = window = {
scope:undefined,
checkscope: function () { }
}
checkscope.[[scope]] = [
globalContext.VO
];
複製代碼
- 執行流遇到 checkscope 調用,建立 checkscope 函數執行上下文,checkscope 函數執行上下文被壓入執行上下文棧
ECStack = [
checkscopeContext,
globalContext
];
複製代碼
- 執行流進入 checkscope 函數上下文,開始準備工做,也就是所謂的 編譯階段 3.1 第一步:複製函數的
[[scope]]
屬性建立屬於本身的做用域鏈,此時並非完整的做用域鏈
checkscopeContext = {
Scope: checkscope.[[scope]],
}
複製代碼
3.2 第二步:用 arguments 建立活動對象,隨後初始化活動對象,加入形參、函數聲明、變量聲明作用域
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: checkscope.[[scope]],
}
複製代碼
3.3 第三步:將建立好的 活動對象 壓入 checkscope 做用域頂端
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: [AO, [[Scope]]]
}
複製代碼
- checscope 函數上下文的做用域鏈、活動對象 建立完畢,準備工做作完,開始執行函數,隨着函數的執行,修改 AO 的屬性值
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: 'local scope'
},
Scope: [AO, [[Scope]]]
}
複製代碼
- 查找到 scope2 的值,返回後函數執行完畢,函數上下文從執行上下文棧中彈出
ECStack = [
globalContext
];
複製代碼
- 在引擎每進入一段可執行代碼段時,會先建立一個執行上下文,將這個執行上下文壓入執行上下文棧;
- 而後執行流進入該執行上下文中,一個執行上下文的執行,分爲兩個階段,編譯階段和執行階段;
- 編譯階段所作的是一些準備工做,
第一步: 先將函數定義時就肯定的父級做用域鏈(也就是詞法上的父級執行環境的變量對象) 複製,做爲當前上下文的做用域鏈,此時的做用域鏈並非完整的; 第二步: 建立 活動對象,隨後初始化活動對象,將上下文中的形參、函數聲明、變量聲明 以鍵值對的形式,存儲在活動對象中,在活動對象建立完畢後; 第三步: 將自身的活動對象壓入本身的做用域鏈的頂端,至此完成做用域鏈的建立。
- 完成上下文的準備階段後,進入代碼執行階段,由上至下順序執行,修改活動對象中的鍵值;
- 當上下文中的代碼執行完畢,將上下文彈出上下文棧,將執行權返還給上層上下文;
- 執行流重複如上步驟,直至執行徹底局上下文。