Execution Context 執行期上下文
在java或c語言中,都有塊級作用域這個概念,而js中則沒有。
在js中,作用域隻有一種,即函數級作用域。
而執行期上下文,可以了解為函數的作用域或執行環境。
在代碼層面,執行期上下文是嵌套存在的

在js引擎内,執行期上下文是以棧的形式進行存放
棧的最底部存放的global上下文,每次執行一個函數,則會建立一個上下文放入棧中,執行結束後再pop移除。
(function foo(i) {
if (i === 3) {
return;
}
else {
foo(++i);
}
}(0));
目前的執行環境則永遠使用存放在棧頂的上下文對象。
參考博文:
- 深入了解Javascript之執行上下文(Execution Context) -- JavaScript -- IT技術部落格大學習 -- 共學習 共進步!
- 深入了解JavaScript系列(10):JavaScript核心(晉級高手必讀篇) - 湯姆大叔 - 部落格園
Function 執行原理
js中有在function上面有很多的用法和概念,就比如作用域鍊,閉包這些。
其實問題歸結到了根本,都在于Function在執行時做了什麼。
這個是以下内容的一個思維導圖:
以Function的生命周期劃分,分為:建立階段 和 執行階段
這裡從執行階段開始,以 執行函數内部代碼 為時間點分成3個階段:
- 執行前
- 執行時
- 執行後
當執行一個函數時,會建立一個函數的執行環境,即執行期上下文對象(execution context)。
ExecutionContext = {
VO:{
// Variable Object(全局上下文獨有)
// 變量 (var, 變量聲明);
// 函數聲明 (FunctionDeclaration, 縮寫為FD);
},
AO:{
// Activation Object (函數上下文獨有)
// 變量 (var, 變量聲明);
// 函數聲明 (FunctionDeclaration, 縮寫為FD);
// arguments
},
scopeChain:{..},
this:{..}
}
該對象主要做了三件事情:
- 确定函數内所有的變量 (AO | VO)
- AO:通過上下文棧中的目前上下文,擷取參數,建立arguments對象
- AO & VO:掃描代碼,建立所有函數聲明(hoist作用域提升的原因,這些函數将進入建立階段)和變量聲明(值為undefined)
- 建立作用域鍊 (scopeChain = (AO | VO) + [[Scope]])
- 确定this指向 (由目前所處的執行期上下文提供)
VO & AO
VO 和 AO 的作用是存儲函數中執行時需要用到的所有函數和變量。
執行期上下文分為兩種:
- 全局上下文 (global,在上下文棧最底部,這個對象隻存在一份,它的屬性在程式中任何地方都可以通路)
- 函數上下文 (每次在函數調用時進行建立)
這兩種上下文的差別就在于其建立方式和VO的通路性。
全局上下文的VO可以直接通路,VO對象指向的是global自身
var name = {};
name === this.name // true
name === window.name // true
函數上下文的VO不能直接通路,建立活動對象AO來代替VO。
是以函數上下文隻有AO,VO沒有。
建立作用域
函數生命周期分為兩個階段:
- 建立階段 (建立
)[[scope]]
- 執行階段 (建立
scopeChain
在函數建立階段,[[scope]]就已經被建立了。
function say(){
var words = "hello";
hello(); // 輸出:hello
function hello(){
console.log(words);
}
}
say.[[scope]] = [
GlobalExecutionContext.VO
]
遵循
[[scope]] = superExecutionContext.scopeChain + superFn.[[scope]]
這個規則。
該變量
[[scope]]
是一種靜态變量,一直存在,直至函數被delete或垃圾回收。
進入執行階段,執行期上下文初始化作用域鍊scopeChain。
say.ExecutionContext = {
AO : {
words : undefined,
arguments : [...]
},
scopeChain : say.[[scope]].concat(this.AO)
}
即
say.ExecutionContext = {
AO : {
words : undefined,
arguments : [...]
},
scopeChain : [
say.ExecutionContext.AO, // 建立自身活動對象
GlobalExecutionContext.VO
]
}
scopeChain = (AO | VO) + [[Scope]]
閉包也遵循這個規則,在say函數執行時,内部函數hello進入建立階段。
hello.[[scope]] = [
say.scopeChain,
GlobalExecutionContext.VO
]
在執行
hello()
時,進入執行階段
hello.ExecutionContext = {
AO : {
arguments : [...]
},
scopeChain : [
hello.ExecutionContext.AO,
say.scopeChain,
GlobalExecutionContext.VO
]
}
- js 中的活動對象 與 變量對象 什麼差別? - JavaScript - 知乎
- 深入了解JavaScript系列(12):變量對象(Variable Object) - 湯姆大叔 - 部落格園
- 深入了解JavaScript系列(14):作用域鍊(Scope Chain) - 湯姆大叔 - 部落格園
确定this執向
this由該函數的執行環境所确定
function person = {
say : function(){
console.log(this);
}
}
person.say() // this指向person對象
var say = person.say;
say() // this指向window
函數的内部代碼執行時,由于在執行前将函數聲明(function關鍵字)和變量聲明(var關鍵字)全部建立到了AO中。
是以會存在一種hoist,即作用域提升的問題。
在函數的内部代碼執行後,會銷毀函數的執行期上下文。
與此同時AO也将被銷毀,除非有引用的情況。
function say(){
var words = "hello";
hello(); // 輸出:hello
function hello(){
console.log(words);
}
return hello; // 将hello抛出
}
var hello = hello.say();
此時的作用域情況如下
window.hello.[[scope]] = [
say.scopeChain : [
say.ExecutionContext.AO,
GlobalExecutionContext.VO
]
]
由于
hello.[[scope]]
中留有
say.scopeChain
say.ExecutionContext.AO
的引用,所有不會被删除。