likes
comments
collection
share

面试官:说说JS中的执行上下文

作者站长头像
站长
· 阅读数 81

⭐说说JS的执行上下文

执行上下文是JavaScript代码执行环境的抽象概念,执行上下文可以看作是一个环境,其中包含了当前代码的变量、函数、this 指向等信息。

但要注意执行上下文与作用域的区别:作用域指的是变量、函数和对象在代码中可访问的范围。它定义了变量的可见性和生命周期。作用域规定了在何处以及如何查找变量。

可以说执行上下文是为了实现作用域而存在的一种机制,但它们并不是完全等同的概念。

执行上下文分为三类:

  1. 全局执行上下文:执行一段JS代码前,会先为它创建全局执行上下文,并将this指向全局对象(在浏览器中是window,node环境中是global)全局执行上下文只会被创建一次,随着程序的开始而创建,随着程序的关闭而销毁。
  2. 函数执行上下文:执行遇到函数时,就会为这个函数创建一个函数执行上下文,但这里的this和函数执行上下文并不等同,只是上下文中保存有this信息
  3. Eval 函数执行上下文: 指的是运行在 eval 函数中的代码,很少用而且不建议使用

🌰举个栗子:

//这里是全局执行上下文
        var globalProperty = 0

function fun1() {
    //这里是fun1的函数执行上下文
        var fun1Property = 1
    
    function fun2() {
        //这里是fun2的函数执行上下文
        var fun2Property = 2
    }
    
    function fun3() {
        //这里是fun3的函数执行上下文
        var fun3Property = 3
    }
}

每次调用函数创建一个新的上下文,会创建一个私有作用域,函数内部声明的任何变量都不能在当前函数作用域外部直接访问。

例如上例中:

  • fun1中可以访问全局执行上下文中的globalProperty,而不能访问内部fun2 fun3中的变量
  • 同理,fun2 fun3可以访问外部的fun1Property以及最外层的globalProperty

执行上下文中存了些什么?

在执行上下文被创建时:

  1. 绑定this,普通声明的函数在执行时才能确定绑定的this值,而es6中箭头函数是词法绑定,被创建时就绑定了所在上下文的this
  2. LexicalEnvironment(词法环境) 组件创建
  3. VariableEnvironment(变量环境) 组件创建
//伪代码流程
ExecutionContext = {  
  ThisBinding = <this value>,     // 确定this 
  LexicalEnvironment = { ... },   // 词法环境
  VariableEnvironment = { ... },  // 变量环境
}

词法环境:

词法环境是一种持有标识符—变量的映射的结构(简单理解为就是保存了我们定义的变量和函数)

词法环境的组成:

  • 环境记录器:是存储变量和函数声明的实际位置。
  • 外部环境的引用:意味着它可以访问其父级词法环境。

词法环境有两种类型:

  • 全局环境:没有外部环境引用的词法环境(环境记录器指向空),它里面保存了JS内置对象、用户定义的全局变量/函数,this绑定为全局对象。
  • 函数环境:用户在函数内部定义的变量存储在环境记录器中,外部环境引用的是外部执行上下文。

根据词法环境的类型不同,环境记录器也分为两类:

  • 全局环境中——对象环境记录器:用来定义出现在全局上下文中的变量和函数的关系,当一个变量或函数在对象环境中被定义或声明时,对象环境记录器会使用一个对象来管理这些变量和函数的绑定关系,通过关联的对象,对象环境记录器实现了对变量和函数的访问和操作 🌰举个栗子:

    var obj = {
      x: 10,
      y: function() {
        console.log('Hello');
      }
    };
    
    console.log(obj.x); // 输出:10
    obj.y(); // 输出:Hello
    
    

    在这个例子中,obj 对象包含了 x 属性和 y 方法。通过对象环境记录器,我们可以通过 obj.x 访问对象的属性值,并通过 obj.y() 调用对象的方法。

  • 函数环境中——声明式环境记录器:存储变量、函数和参数。

词法环境与对象环境记录器在内部机制上有所不同。其中最明显的区别是词法环境使用一个特殊的数据结构——词法环境记录器(Lexical Environment Record),而对象环境记录器则使用一个普通的 JavaScript 对象

//词法环境伪代码
GlobalExectionContext = {        // 全局执行上下文
    LexicalEnvironment: {        // 词法环境
        EnvironmentRecord: {     // 环境记录器:存储变量和函数声明的实际位置
            Type: "Object",      
            // 在这里绑定标识符  
        }
        outer: <null>           // 对外部环境的引用:可以访问其父级词法环境
    }
}

FunctionExectionContext = {     // 函数执行上下文
    LexicalEnvironment: {
        EnvironmentRecord: {
            Type: "Declarative",
            // 在这里绑定标识符
        }
        outer: <Global or outer function environment reference>
    }
}

变量环境:

变量环境也是一个词法环境,因此它具有上面定义的词法环境的所有属性

在 ES6 中,词法环境和变量环境的区别在于前者用于存储函数声明和变量( 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 >,   // let、const声明的变量
      b: < uninitialized >,   // let、const声明的变量
      multiply: < func >      // 函数声明
    }
    outer: <null>
  },
  VariableEnvironment: {     // 变量环境
    EnvironmentRecord: {     
      Type: "Object",
      // 在这里绑定标识符
      c: undefined,         // var声明的变量
    }
    outer: <null>
  }
}

FunctionExectionContext = {
  ThisBinding: <Global Object>,
  LexicalEnvironment: {         // 词法环境
    EnvironmentRecord: {   
      Type: "Declarative",
      // 在这里绑定标识符
      Arguments: {0: 20, 1: 30, length: 2},   // arguments对象
    },
    outer: <GlobalLexicalEnvironment>
  },
  VariableEnvironment: {        // 变量环境
     EnvironmentRecord: {
       Type: "Declarative",
       // 在这里绑定标识符
       g: undefined            // var声明的变量
     },
     outer: <GlobalLexicalEnvironment>
  }
}

在其中,let和const定义的关联值设为了uninitialized,说明为初始化,而var则被初始化为了undefined,这就是let/const和var的本质区别

综上,JS程序执行的全过程大致可以描述为:

程序启动,全局执行上下文被创建,压入调用栈

  1. 创建全局上下文的 词法环境

    1. 创建 对象环境记录器 ,它用来定义出现在 全局上下文 中的变量和函数的关系(负责处理 letconst 定义的变量)
    2. 创建 外部环境引用,值为 null
  2. 创建全局上下文的 变量环境

    1. 创建 对象环境记录器,它持有 变量声明语句 在执行上下文中创建的绑定关系(负责处理 var 定义的变量,初始值为 undefined 造成声明提升)
    2. 创建 外部环境引用,值为 null
  3. 确定 this 值为全局对象(以浏览器为例,就是 window

函数被调用,函数执行上下文被创建,压入调用栈

  1. 创建函数上下文的 词法环境

    1. 创建 声明式环境记录器 ,存储变量、函数和参数,它包含了一个传递给函数的 arguments 对象(此对象存储索引和参数的映射)和传递给函数的参数的 length。(负责处理 letconst 定义的变量)
    2. 创建 外部环境引用,值为全局对象,或者为父级词法环境(作用域)
  2. 创建函数上下文的 变量环境

    1. 创建 声明式环境记录器 ,存储变量、函数和参数,它包含了一个传递给函数的 arguments 对象(此对象存储索引和参数的映射)和传递给函数的参数的 length。(负责处理 var 定义的变量,初始值为 undefined 造成声明提升)
    2. 创建 外部环境引用,值为全局对象,或者为父级词法环境(作用域)
  3. 确定 this

进入函数执行上下文的执行阶段:

  1. 在上下文中运行/解释函数代码,并在代码逐行执行时分配变量值。

参考文章:

面试官:JavaScript中执行上下文和执行栈是什么? | web前端面试 - 面试官系列 (vue3js.cn)

转载自:https://juejin.cn/post/7281465630494081035
评论
请登录