都说想成为出色的 JavaScript 开发者,就要深入学习 JavaScript 程序内部的执行机制,最近学了一遍 JS 的执行上下文和执行栈,以此作总结。
• EC:函数执行环境
(或执行上下文)
,Execution Context
•
ECS:执行环境栈,Execution Context Stack
•
VO:变量对象,Variable Object
每次当控制器转到 ECMAScript 可执行代码的时候,它都是在执行上下文中运行,即是指当前执行环境中的变量、函数声明,参数,作用域链,this 等信息。
组成代码示例
const ExecutionContextObj = {
VO: window,
ScopeChain: {},
this: window
};
1. 全局
执行上下文
—— 这是默认上下文,浏览器中的全局对象就是 window 对象,任何不在函数内部的代码都在全局上下文中,this 指向这个全局对象。
2. 函
数执行上下文
—— 当函数被调用时创建,会为该函数创建一个新的执行上下文,可以有任意个。
3. Eva
l 函数执行上
下文
—— 执行 eval 函数内部的代码也有属于它的上下文,由于开发中是尽量避免或不用 eval 函数,故此不作讨论。
执行栈,也叫调用栈,被用来存储代码运行时创建的所有执行上下文。
当 JavaScript 引擎第一次遇到脚本时,它会创建一个全局的执行上下文并且压入当前执行栈。每当引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入栈的顶部。
引擎会执行那些执行上下文位于栈顶的函数。当该函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文。
function fn1() {
console.log('fn1被调用了 -- 创建了fn1的函数执行上下文,压入栈');
fn2();
console.log('fn2执行完成,fn2的执行上下文会从栈中弹出');
}
function fn2() {
console.log('fn2被调用了 -- 创建了fn2的函数执行上下文,压入栈');
}
fn1();
console.log('fn1执行完成,fn2的执行上下文会从栈中弹出');
fn1被调用了 -- 创建了fn1的函数执行上下文,压入栈
fn2被调用了 -- 创建了fn2的函数执行上下文,压入栈
fn2执行完成,fn2的执行上下文会从栈中弹出
fn1执行完成,fn2的执行上下文会从栈中弹出
当上述代码在浏览器加载时,JavaScript 引擎创建了一个全局执行上下文并把它亚压入栈中,当函数 fn1() 被调用时,JavaScript 为该函数创建了一个函数执行上下文,并把它压入当前执行栈的顶部。
当 fn1() 函数内部调用 fn2() 函数时,JavaScript 引擎同样创建了 fn2() 的函数执行上下文并压入栈的顶部。然后执行了 fn2() 函数后,fn2() 函数会从当前栈
(后进先出结构)
弹出,并且按程序执行顺序继续执行 fn1() 函数,即此刻处于 fn1 的函数执行上下文。
当 fn1() 函数执行完毕,它的执行上下文从栈弹出,控制流程到达全局执行上下文。一旦所有代码执行完毕,JavaScript 引擎从当前栈中移除全局执行上下文。
已经知道 JavaScript 怎样管理执行上下文了,现在来了解 JavaScript 引擎是怎么创建执行上下文的。
在 JavaScript 代码执行前,执行上下文将经历创建阶段。
在创建阶段会发生三件事:
2. 创建
(LexicalEnvironment)
词法环境组件
3. 创建
(VariableEnvironment)
变量环境组件
ExecutionContext = {
ThisBinding = ,
LexicalEnvironment = { ... },
VariableEnvironment = { ... },
}
(一)创建阶段
this 绑定
在全局执行上下文中,this 的值指向全局对象。
(在浏览器中,this 引用 window 对象)
在函数执行上下文中,this 的指向取决于函数是如何被调用的,在本篇暂不对 this 指向做详细讨论。