1.执行栈
当js代码执行进入一个环境时,就会为该环境创建一个执行上下文,它会为运行代码前做一些准备:确定作用域、this指向、变量对象。
执行环境有:
- 全局环境
- 函数环境
- eval环境
执行上下文有:
- 全局执行上下文
- 函数执行上下文
- eval上下文
JavaScript运行代码时,就会进入全局环境,对应生成全局执行上下文,若代码中存在函数,则调用函数时,会进入到函数环境,生成函数执行上下文。
由于代码中存在多个函数调用,所以也会存在多个函数执行上下文,在JavaScript中,通过栈的存取来管理这些上下文,我们称其为执行栈(函数调用栈)
2.栈的结构
栈采用先进后出,后进先出原则,即压栈和出栈操作,出口只有一个
JavaScript运行代码时,会先进入全局环境,然后创建全局执行上下文,并将上下文压入栈中,接着执行全局代码,当遇到函数调用时,会进入函数上下文,然后创建函数执行上下文,并压入栈中,若函数执行过程中又遇到函数调用,则继续生成函数执行上下文并压入栈中,当函数执行完毕后,会进行出栈操作,即将执行完毕的函数执行上下文弹出栈。
故栈底永远只有一个:全局执行上下文,只有当页面被销毁后,它才会被释放(弹出栈)
栈顶为正则执行的函数执行上下文
执行上下文的数量限制(堆栈溢出) 执行上下文可存在多个,没有数量限制,但当执行上下文的占用的空间超过栈空间分配的空间时,会造成堆栈溢出。常见于递归调用,没有终止条件造成死循环的场景
3.执行上下文的生命周期
执行上下文的生命周期有两个:
- 创建阶段(进入执行上下文):函数被调用,进入函数环境,创建函数执行上下文,然后进入执行阶段
- 执行阶段(代码执行):执行函数中的代码,此时已经进入执行阶段
创建阶段主要做:
- 创建变量对象VO
- 确定函数的形参并赋值
- 函数环境会初始化arguments对象并赋值
- 确定字面量形式的函数声明并赋值
- 变量声明(变量提升),未赋值
- 确定this指向
- 确定作用域
注意
- 在这一阶段中,若存在同名的字面量函数声明和var声明的变量,由于会优先进行函数声明,所以会忽略同名的变量声明。
executionContextObj = {
variableObject : {}, // 变量对象,里面包含 Arguments 对象,形式参数,函数和局部变量
scopeChain : {},// 作用域链,包含内部上下文所有变量对象的列表
this : {}// 上下文中 this 的指向对象
}
执行阶段:
- 变量对象赋值
- 变量赋值
- 函数表达式赋值(非字面量声明)
- 调用函数
- 按顺序执行代码
const foo = function(i){
var a = "Hello";
var b = function privateB(){};
function c(){}
}
foo(10);
- 上面的代码首先进入全局环境,创建全局执行上下文,然后进行压栈操作
- 接着执行代码,遇到foo函数调用,进入foo函数环境,创建函数执行上下文
ecutionContextFoo = {
// 变量对象
VO:{
arguments:{
length:1,
c:function c,
a:undefined,
b:undefined
}
},
scopeChain:[],
this:{}
}