這是我參與8月更文挑戰的第5天,活動詳情查看:8月更文挑戰javascript
當 JavaScript 代碼執行一段可執行代碼(executable code)時,會建立對應的執行上下文(execution context),能夠理解是一個對象。java
當執行 JS 代碼時,會產生三種執行上下文:數組
每一個執行上下文中都有三個重要的屬性:markdown
爲了管理執行上下文,因此 JavaScript 引擎建立了執行上下文棧(Execution context stack,ECS)。app
var a = 10;
function foo(i) {
var b = 20;
}
foo();
複製代碼
對於上述代碼,執行棧中有兩個上下文:全局上下文和函數 foo 上下文。ide
試想當 JavaScript 開始要解釋執行代碼的時候,最早遇到的就是全局代碼,因此初始化的時候首先就會向執行上下文棧壓入一個全局執行上下文,咱們用 globalContext
表示它,而且只有當整個應用程序結束的時候,stack
纔會被清空。函數
stack = [ globalContext, fooContext]
複製代碼
對於全局上下文來講,VO 大概是這樣的:post
globalContext.VO === globe
globalContext.VO = {
a: undefined,
foo: <Function>,
}
複製代碼
對於函數 foo 來講,VO 不能訪問,只能訪問到活動對象(AO)ui
// arguments 是函數獨有的對象(箭頭函數沒有)
// 該對象是一個僞數組,有 `length` 屬性且能夠經過下標訪問元素
// 該對象中的 `callee` 屬性表明函數自己
// `caller` 屬性表明函數的調用者
fooContext.VO === foo.AO
fooContext.AO = {
i: undefined,
b: undefined,
arguments: <> } 複製代碼
對於做用域鏈,能夠把它理解成包含自身變量對象和上級變量對象的列表,經過 [[Scope]]
屬性查找上級變量this
fooContext.[[Scope]] = [
globalContext.VO
]
fooContext.Scope = fooContext.[[Scope]] + fooContext.VO
// so
fooContext.Scope = [
fooContext.VO,
globalContext.VO
]
複製代碼
function fun3() {
console.log("fun3");
}
function fun2() {
fun3();
}
function fun1() {
fun2();
}
fun1();
複製代碼
當執行一個函數的時候,就會建立一個執行上下文,而且壓入執行上下文棧,當函數執行完畢的時候,就會將函數的執行上下文從棧中彈出。知道了這樣的工做原理,讓咱們來看看如何處理上面這段代碼:
// 僞代碼
// fun1()
ECStack.push(<fun1> functionContext);
// ECStack = [globalContext, fun1Context]
// fun1中居然調用了fun2,還要建立fun2的執行上下文
ECStack.push(<fun2> functionContext);
// ECStack = [globalContext, fun1Context, fun2Context]
// 擦,fun2還調用了fun3!
ECStack.push(<fun3> functionContext);
// ECStack = [globalContext, fun1Context, fun2Context, fun3Context]
// fun3執行完畢
ECStack.pop();
// ECStack = [globalContext, fun1Context, fun2Context]
// fun2執行完畢
ECStack.pop();
// ECStack = [globalContext, fun1Context]
// fun1執行完畢
ECStack.pop();
// ECStack = [globalContext]
// javascript接着執行下面的代碼,可是ECStack底層永遠有個globalContext
複製代碼