JS进阶笔记(一)——执行上下文

前言

本文是学习执行上下文时做的学习笔记,非原创,只是对知识的整理。
参考资料:

什么是执行上下文?

执行上下文是 评估和执行 js 代码的环境 的抽象概念。每当 js 代码在运行的时候,它都是在执行上下文中运行。

每个执行上下文都有三个重要属性:

  • 变量对象 (Variable object, VO)
  • 作用域链 (Scope chain)
  • this

执行上下文的三种类型

  • 全局执行上下文 —— 任何不在函数内部的代码都在全局上下文中。它会执行两件事:创建一个全局的 window 对象(浏览器的情况下),并且设置 this 的值等于这个全局对象。一个程序中只会有一个全局执行上下文。
  • 函数执行上下文 —— 每一个函数被调用时,都会为该函数创建一个新的上下文。每个函数都有自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。

    在函数上下文中,我们用 活动对象 (activation object, AO) 来表示变量对象。

    活动对象和变量对象其实是一个东西,只是变量对象是规范上的或者说是引擎实现上的,不可在 JavaScript 环境中访问,只有到当进入一个执行上下文中,这个执行上下文的变量对象才会被激活,所以才叫 activation object ,而只有被激活的变量对象,也就是活动对象上的各种属性才能被访问。

    活动对象是在进入函数上下文时刻被创建的,它通过函数的 arguments 属性初始化。arguments 属性值是 Arguments 对象。

  • Eval 函数执行上下文 —— eval() 函数可计算某个字符串,并执行其中的 js 代码。 执行在 eval 函数内部的代码叶惠有它自己的执行上下文。不常用。

执行栈

执行栈,也在其他编程语言中称为“调用栈”,是一种拥有 LIFO (后进先出)数据结构的栈,被用来 存储代码运行时创建的所有执行上下文

当 js 引擎第一次遇到你的脚本时,它会创建一个全局的执行上下文并且压入当前执行栈。每当引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入栈的顶部。

引擎会执行那些执行上下文位于栈顶的函数。当该函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文。

执行上下文的生命周期

创建

  • 生成变量对象
  • 创建作用域链
  • 确定 this指向

执行

  • 变量赋值

执行完毕后出栈,等待被回收

创建阶段

在 js 代码执行前,执行上下文将经历创建阶段。在创建阶段会发生三件事:

  1. this 绑定

    在全局执行上下文中,this 的值指向全局对象。在浏览器中,this 引用 Window 对象。

    在函数执行上下文中,this 的值取决于该函数是如何被调用的。如果它被一个引用对象调用,那么 this 会被设置成那个对象,否则 this 的值就是全局对象或者 undefined(严格模式下)。例如:

    let foo = {
      baz: function() {
        console.log(this);
      }
    }
    
    foo.baz(); // 'foo' 
    
    let bar = foo.baz;
    
    bar(); // window 因为没有指定引用对象
    
  1. 创建 词法环境 组件

    ES6文档定义:

    词法环境 是一种规范类型,基于 ECMAScript 代码的词法嵌套结构来定义标识符和具体变量和函数的关联。一个词法环境由环境记录器和一个可能的引用外部词法环境的空值组成。

    简单来说 词法环境 是一种持有标识符—变量映射的结构。(这里的标识符指的是变量/函数的名字,而变量是对实际对象[包含函数类型对象]或原始数据的引用)。

    现在,在词法环境的内部有两个组件:(1) 环境记录器和 (2) 一个外部环境的引用

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

    词法环境有两种类型:

    • 全局环境(在全局执行上下文中)是没有外部环境引用的词法环境。全局环境的外部环境引用是 null。它拥有内建的 Object/Array/等、在环境记录器内的原型函数(关联全局对象,比如 window 对象)还有任何用户定义的全局变量,并且 this的值指向全局对象。
    • 函数环境中,函数内部用户定义的变量存储在环境记录器中。并且引用的外部环境可能是全局环境,或者任何包含此内部函数的外部函数。

    环境记录器也有两种类型(如上!):

    1. 声明式环境记录器存储变量、函数和参数。
    2. 对象环境记录器用来定义出现在全局上下文中的变量和函数的关系。

简而言之,

  • 全局环境中,环境记录器是对象环境记录器。
  • 函数环境中,环境记录器是声明式环境记录器。

注意 — 对于函数环境声明式环境记录器还包含了一个传递给函数的 arguments 对象(此对象存储索引和参数的映射)和传递给函数的参数的 length

  1. 创建 变量环境 组件

    它同样是一个词法环境,其环境记录器持有变量声明语句在执行上下文中创建的绑定关系。

    如上所述,变量环境也是一个词法环境,所以它有着上面定义的词法环境的所有属性。

    在 ES6 中,词法环境组件和变量环境的一个不同就是前者被用来存储函数声明和变量(letconst)绑定,而后者只用来存储 var 变量绑定。

因此,变量对象会包括

  1. 函数的所有形参 (如果是函数上下文)
    • 由名称和对应值组成的一个变量对象的属性被创建
    • 没有实参,属性值设为 undefined
  2. 函数声明
    • 由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建
    • 如果变量对象已经存在相同名称的属性,则完全替换这个属性
  3. 变量声明
    • 由名称和对应值(undefined)组成一个变量对象的属性被创建;
    • 如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性
执行阶段

在此阶段,顺序执行代码,完成对所有这些变量的分配,修改变量对象的值。

在执行阶段,如果 js 引擎不能在源码中声明的实际位置找到 let 变量的值,它会被赋值为 undefined

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容