1.什么是执行上下文
执行上下文是当前JavaScript代码被解析和执行时所在环境的抽象概念, JavaScript中运行任何代码都是在执行上下文中运行的
2.执行上下文的类型
执行上下文共有三种类型
- 全局执行上下文: 只有一个, 全局代码的执行环境, 不在函数中的代码都位于全局执行上下文. 它做了两件事:
- 创建一个全局对象, 在浏览器中这个全局对象就是window对象;
- 将
this
指针指向这个全局对象.
- 函数执行上下文: 存在无数个, 每次调用函数时, 都会为这个函数创建一个新的函数执行上下文.
- eval函数执行上下文: 运行在
eval
函数中的代码也会创建执行上下文, 但在js很少也不推荐使用eval函数
3. 执行上下文栈
执行栈,也叫调用栈,具有 LIFO(后进先出)结构,用于存储在代码执行期间创建的所有执行上下文。
首次运行JS代码时,会创建一个全局执行上下文并Push到当前的执行栈中。每当发生函数调用,引擎都会为该函数创建一个新的函数执行上下文并Push到当前执行栈的栈顶。
根据执行栈LIFO规则,当栈顶函数运行完成后,其对应的函数执行上下文将会从执行栈中Pop出,上下文控制权将移到当前执行栈的下一个执行上下文。
var a = 'Hello World!';
function first() {
console.log('Inside first function');
second();
console.log('Again inside first function');
}
function second() {
console.log('Inside second function');
}
first();
console.log('Inside Global Execution Context');
// -> stack: [Global]
// Inside first function -> stack: [Global, first] (push)
// Inside second function -> stack: [Global, first, second] (push)
// Again inside first function -> stack: [Global, first] (pop)
// Inside Global Execution Context -> stack: [Global] (pop)
4.执行上下文的生命周期
执行上下文的生命周期包含三个阶段: 创建阶段 -> 执行阶段 -> 回收阶段
- 创建阶段
当函数被调用, 未执行任何其内部代码时, 会做以下三件事:- 确定this的值, 又称
This Binding
- this的值是在执行时确定的, 而不是定义的时候
- 在全局执行上下文中,
this
的值指向全局对象, 在浏览器中指向window
- 在函数执行上下文中,
this
的值取决于函数的调用方式, 如果它被一个对象引用调用, 那么this的值被设置为该对象, 否则this
的值指向全局对象
或undefined
(严格模式).
- LexicalEnvironment(词法环境) 组件被创建
见下方详解 - VariableEnvironment(变量环境) 组件被创建
见下方详解
- 确定this的值, 又称
- 执行阶段
从上至下变量赋值, 执行代码 - 回收阶段
执行上下文出栈, 等待虚拟机回收执行上下文
5.LexicalEnvironment(词法环境)
词法环境就是描述环境的对象,主要包含两个部分:
- 环境记录(Environment Record)
记录相应环境中的形参(arguments
),函数声明,变量声明等 - 对外部环境的引用(out reference)
词法环境有两种类型:
- 全局变量: 是一个没有外部环境的词法环境, 其外部环境引用为null, 有一个全局对象,
this
的值指向这个全局对象 - 函数环境: 用户在函数中定义的变量被存储在环境记录(environment record)中, 包含了
arguments
对象. 对外部环境的引用可以是全局环境, 也可以是包含内部函数的外部函数环境
伪代码:
GLobalExectionContext = { // 全局执行上下文
LexicalEnvironment: { // 词法环境
EnvironmentRecord: { // 环境记录
Type: "Object", // 全局环境
// 标识符绑定在这里
}
outer: <null> // 对外部环境的引用
}
}
FunctionExectionContext = { // 函数执行上下文
LexicalEnvironment: { // 词法环境
EnvironmentRecord: { // 环境记录
Type: "Declarative", // 函数环境
// 标识符绑定在这里
}
outer: <Global or outer function environment reference> // 对外部环境的引用
}
}
6.VariableEnvironment(变量环境)
它也是一个词法环境,其 EnvironmentRecord
包含了由 VariableStatements
在此执行上下文创建的绑定。
如上所述,变量环境也是一个词法环境,因此它具有上面定义的词法环境的所有属性。
在 ES6
中,LexicalEnvironment
组件和 VariableEnvironment
组件的区别在于前者用于存储函数声明和变量(let
和const
)绑定,而后者仅用于存储变量(var
)绑定。
示例:
let a = 20;
const b = 30;
var c;
function multiply(e, f) {
var g = 20;
return e * f * g
}
c = multiply(20, 30)
执行上下文如下:
GlobalExectionContext = { // 全局执行上下文
ThisBinding: <Global Object>,
LexicalEnvironment: { // 词法环境
EnvironmentRecord: {
Type: "Object",
// 标识符绑定在这里
a: <uninitialized>,
b: <uninitialized>,
mutilply: <func>
},
outer: <null>
},
VariableEnvironment: { // 变量环境
EnvironmentRecord: {
Type: "Object",
// 标识符绑定在这里,
c: undefined,
}
outer: <null>
}
}
FunctionExectionContext = { // 函数执行上下文
ThisBinding: <Global Object>,
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// 标识符绑定在这里
Arguments: {0: 20, 1: 30, length: 2}
},
outer: <GlobalLexicalEnvironment>
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// 标识符绑定在这里
g: undefined
},
outer: <GlobalLexicalEnvironment>
}
}
注意: 只有在遇到函数 multiply 的调用时才会创建函数执行上下文。
你可能已经注意到了let
和const
定义的变量没有任何与之关联的值,但var
定义的变量设置为undefined
这是因为在创建阶段,代码会被扫描并解析变量和函数声明,其中函数声明存储在环境中,而变量会被设置为undefined
(在var
的情况下)或保持未初始化(在let
和const
的情况下)。
这就是为什么你可以在声明之前访问var
定义的变量(尽管是undefined
),但如果在声明之前访问let
和const
定义的变量就会提示引用错误的原因。
这就是所谓的变量提升。