执行栈与执行上下文
本文讨论 ES5 中的执行上下文与执行栈的概念
省流:
执行上下文(EC)形成于 JavaScript 解释器执行代码的过程中,负责准备代码执行的环境。
分为三类,全局、函数和 eval
包括变量(VO)、作用域链、和 this
其生命周期有两个阶段:创建阶段和执行阶段
创建
创建 VO
找形参赋值
找 arguments 赋值
函数声明赋值
var 赋值 undefined
确定 this
确定作用域
执行
- 上阶段的变量赋值(已经有值的就覆盖了)
- 逐行执行
执行栈也称调用栈,是 JavaScript 解析器管理执行上下文的结构或机制
执行上下文
Execution Context
,负责代码(全局、函数)执行之前的准备工作,即代码执行的环境,也称执行上下文环境
- 全局执行上下文
JavaScript 在运行时,首先会进入全局环境,生成全局执行上下文。首先创建一个全局的 window 对象,并且设置 this 的值等于这个全局对象。一个程序中有且仅有一个全局上下文。
- 函数执行上下文
在调用函数时,就会创建一个函数上下文
- Eval 函数执行上下文
执行在 eval 函数内部的代码也会有它属于自己的执行上下文
执行栈(调用栈)
执行栈也称调用栈,就是 JavaScript 解析器管理执行上下文的结构或机制
当程序进入一个执行环境时,它的执行上下文对象就会被创建并入栈;程序执行完成时,它的执行上下文就会被摧毁并出栈
处于栈底的一定是全局执行上下文,而处于栈顶的是当前正在执行函数的上下文
如果执行上下文数量超过栈分配的空间,会造成栈溢出,常见于递归调用,没有终止条件造成死循环的场景
执行上下文的生命周期
执行上下文具体会做哪些准备工作呢?
执行上下文的生命周期共包含两个阶段:
创建阶段
创建变量对象的行为也是面试中的主要考点
- 创建变量对象(
VO: variable object
)- 确定函数的形参(并赋值)
- 函数环境会初始化 arguments 对象(并赋值)
- 确定普通字面量形式的函数声明(并赋值)
- var 变量声明,var 函数表达式声明(未赋值)
- 对于已经声明的变量,不会重复声明
- 确定
this
指向 - 确定作用域
executionContextObj = {
variableObject: {}, // 变量对象,包括 arguments 对象、形参、函数和局部变量
scopeChain: {},
this: {}
}
示例:
const foo = function(i) {
console.log(b)
console.log(c)
var a = "Hello"
var b = function privateB() {}
function c(){}
}
foo(10)
// 生成的 VO 为
VO = {
i: 10, // 1
arguments: { 0: 10, length: 1 }, // 2
c: pointer to funcC, // 3
a: undefined, // 4
b: undefined, // 4
}
// 因此输出结果为 undefined 与 [Function c]
执行阶段
- 变量对象赋值
- 变量赋值
- 函数表达式赋值
- 调用函数
- 顺序执行其它代码
以下是代码示例
(function (){
console.log(typeof foo)
console.log(typeof bar)
var foo = "Hello"
var bar = function (){
return "World"
}
function foo() {
return "good"
}
console.log(foo, typeof foo)
})()
// 函数立即执行,创建阶段的 VO
VO = {
arguments: { length: 0 },
foo: pointer to function foo(){}, // 普通字面量形式的函数声明
// foo 已经声明过了,所以 var foo 在创建阶段不生效
bar: undefined
}
// 执行阶段,逐行执行
// 打印 function
// 打印 undefined
VO = {
arguments: { length: 0 },
foo: 'Hello'
bar: pointer to function (){},
}
// 最后打印 'Hello', string
IIFE
立即执行函数表达式(Immediately Invoked Function Expression,IIFE)是在定义后立即执行的JavaScript函数。IIFE的主要用途包括:
- 创建新的作用域:在ES5之前,JavaScript没有块级作用域,只有全局作用域和函数作用域。IIFE允许开发者为变量和函数创建一个新的局部作用域,避免污染全局命名空间。
- 封装私有变量(结合闭包):通过IIFE,可以封装私有变量和函数,提供一个隔离的空间,在这个空间内,变量和函数不会暴露到全局作用域,从而防止外部的访问和冲突。
- 模块模式:在模块系统出现之前,IIFE常用于模拟模块模式(Module Pattern),通过这种方式可以模拟出公共API的暴露和私有变量的封装。
- 避免临时变量泄露:在循环或者复杂的计算中,可以使用IIFE来避免中间变量泄露到外部作用域。
- 初始化工作:当你需要在页面加载时执行一些初始化代码,但又不想创建额外的命名函数时,可以使用IIFE来执行这些引导代码。