前端面试-js-执行栈和执行上下文

34 阅读3分钟

1.执行栈

当js代码执行进入一个环境时,就会为该环境创建一个执行上下文,它会为运行代码前做一些准备:确定作用域、this指向、变量对象。
执行环境有:

  • 全局环境
  • 函数环境
  • eval环境

执行上下文有:

  • 全局执行上下文
  • 函数执行上下文
  • eval上下文

JavaScript运行代码时,就会进入全局环境,对应生成全局执行上下文,若代码中存在函数,则调用函数时,会进入到函数环境,生成函数执行上下文。

由于代码中存在多个函数调用,所以也会存在多个函数执行上下文,在JavaScript中,通过栈的存取来管理这些上下文,我们称其为执行栈(函数调用栈)

2.栈的结构

栈采用先进后出,后进先出原则,即压栈和出栈操作,出口只有一个

image.png

JavaScript运行代码时,会先进入全局环境,然后创建全局执行上下文,并将上下文压入栈中,接着执行全局代码,当遇到函数调用时,会进入函数上下文,然后创建函数执行上下文,并压入栈中,若函数执行过程中又遇到函数调用,则继续生成函数执行上下文并压入栈中,当函数执行完毕后,会进行出栈操作,即将执行完毕的函数执行上下文弹出栈。
故栈底永远只有一个:全局执行上下文,只有当页面被销毁后,它才会被释放(弹出栈)
栈顶为正则执行的函数执行上下文

执行上下文的数量限制(堆栈溢出) 执行上下文可存在多个,没有数量限制,但当执行上下文的占用的空间超过栈空间分配的空间时,会造成堆栈溢出。常见于递归调用,没有终止条件造成死循环的场景

image.png

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:{}
}