【前端面经】JS-执行上下文和执行上下文栈

1.什么是执行上下文

执行上下文是当前JavaScript代码被解析和执行时所在环境的抽象概念, JavaScript中运行任何代码都是在执行上下文中运行的

2.执行上下文的类型

执行上下文共有三种类型

  1. 全局执行上下文: 只有一个, 全局代码的执行环境, 不在函数中的代码都位于全局执行上下文. 它做了两件事:
    1. 创建一个全局对象, 在浏览器中这个全局对象就是window对象;
    2. this指针指向这个全局对象.
  2. 函数执行上下文: 存在无数个, 每次调用函数时, 都会为这个函数创建一个新的函数执行上下文.
  3. 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.执行上下文的生命周期

执行上下文的生命周期包含三个阶段: 创建阶段 -> 执行阶段 -> 回收阶段

  1. 创建阶段
    当函数被调用, 未执行任何其内部代码时, 会做以下三件事:
    1. 确定this的值, 又称This Binding
      • this的值是在执行时确定的, 而不是定义的时候
      • 在全局执行上下文中, this的值指向全局对象, 在浏览器中指向window
      • 在函数执行上下文中, this的值取决于函数的调用方式, 如果它被一个对象引用调用, 那么this的值被设置为该对象, 否则this的值指向全局对象undefined(严格模式).
    2. LexicalEnvironment(词法环境) 组件被创建

      见下方详解
    3. VariableEnvironment(变量环境) 组件被创建

      见下方详解
  2. 执行阶段

    从上至下变量赋值, 执行代码
  3. 回收阶段

    执行上下文出栈, 等待虚拟机回收执行上下文

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 组件的区别在于前者用于存储函数声明和变量(letconst)绑定,而后者仅用于存储变量(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 的调用时才会创建函数执行上下文。

你可能已经注意到了 letconst 定义的变量没有任何与之关联的值,但 var 定义的变量设置为 undefined

这是因为在创建阶段,代码会被扫描并解析变量和函数声明,其中函数声明存储在环境中,而变量会被设置为 undefined(在 var 的情况下)或保持未初始化(在 letconst 的情况下)。

这就是为什么你可以在声明之前访问 var 定义的变量(尽管是 undefined ),但如果在声明之前访问 letconst 定义的变量就会提示引用错误的原因。

这就是所谓的变量提升

参考文章

  1. 博客园-深入理解JavaScript执行上下文和执行栈
  2. CSDN-JS中执行上下文与作用域
  3. 面试官-JavaScript中执行上下文和执行栈是什么?
  4. 掘金-【译】理解 Javascript 执行上下文和执行栈
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深海大凤梨_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值