【JavaScript】【作用域】执行上下文和执行栈

前言

可以通过我下面两篇文章简单的了解一下,作用域相关的概念:

在这篇文章中我们主要是对 执行上下文、执行栈 做个介绍。本文是基于 ES6

一、什么是执行上下文

官方一点地说,执行上下文(Execution context stack 简称 ECS)就是一个评估和执行JavaScript代码的环境的抽象概念。通俗地说,就是每当 Javascript 代码在运行的时候,它都是在执行上下文中运行。

通俗的解释,就是一段代码执行时所带的所有信息。包括变量、函数声明、参数(arguments)、作用域脸、this等信息。

在不同 ECMAScript 版本中执行上下文所代表的含义:

  • ES3
    • scope:作用域,作用域链
    • variable object: 变量对象,用来存储变量的对象
    • this value: this 值
  • ES5 ,改进了命名方式,把执行上下文最初的三个部分改成下面的样子
    • lexical environment:词法环境,当获取变量时使用
    • variable environment:变量环境,当声明变量时使用
    • this value: this 值
  • ES2018 ,this 值被归入 lexical environment,但是增加了不少内容
    • lexical environment:词法环境,当获取变量或者 this 值时使用
    • variable environment:变量环境,当声明变量时使用
    • code evaluation state: 用于恢复代码执行位置
    • Function:执行的任务是函数时使用,表示正在被执行的函数
    • ScriptOrModule:执行的任务是脚本或者模块时使用,表示正在被执行的代码
    • Realm:使用的基础库和内置对象实力
    • Generator:仅生成器上下文有这个属性,表示当前生成器

二、执行上下文的类型

JavaScript中有三种执行上下文类型:

  • 全局执行上下文 :一个程序中只有一个全局执行上下文,任何不在函数内部的代码,都属于全局上下文,也就意味着this的指向是window对象(浏览器环境下)
  • 函数执行上下文:是在函数被调用的时候创建的,函数上下文可以有很多个,调用一次函数生成一个,执行顺序是函数调用的顺序
  • eval执行上下文 :是在执行eval函数内部的代码是会有子级的执行上下文(在日常开发不会用到)

三、执行上下文的生命周期

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

3.1 创建阶段

JavaScript代码执行前,执行上下文将经历创建阶段。在创建阶段需要做的三件事:

  • this值的决定,就是this绑定
  • 创建此法环境组件(lexical environment)
  • 创建变量环境组件(variable environment)

1) this绑定

  • 在全局执行上下文中,this对象指向全局对象(浏览器环境值得是window)

  • 在函数执行上下文中,this取决于该函数是否调用,且被什么调用。

    • 如果是一个引用对象调用,那么this会被设置成那个对象
    • 如果函数没有被调用,那么此时this的值会被设置成 全局对象或者undefined

    严格模式下,如果函数没有被绑定在任何对象上,函数执行上下文中的this会被设置为 undefined。这是因为在这种情况下,函数没有任何上下文可依赖,因此this被设置为undefined

js 复制代码
let foo = {
  baz: function() {
  console.log(this);
  }
}

foo.baz();   // 'this' 引用 'foo', 因为 'baz' 被
             // 对象 'foo' 调用

let bar = foo.baz;

bar();       // 'this' 指向全局 window 对象,因为
             // 没有指定引用对象

2) 词法环境

官方的 ES6[1] 文档把词法环境定义为

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

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

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

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

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

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

    • 对象环境记录器用来定义出现在全局上下文中的变量和函数的关系

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

3) 变量环境

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

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

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

示例代码:

js 复制代码
let a = 20;
const b = 30;
var c;

function multiply(e, f) {
 var g = 20;
 return e * f * g;
}

c = multiply(20, 30);

执行上下文看起来像这样:

js 复制代码
GlobalExectionContext = {

  ThisBinding: <Global Object>,

  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // 在这里绑定标识符
      a: < uninitialized >,
      b: < uninitialized >,
      multiply: < 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>
  }
}

3.2 执行阶段

在此阶段,完成对所有这些变量的分配,最后执行代码

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

3.3 回收阶段

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

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

四、执行栈

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

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

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

下面我们通过一个示例来理解:

js 复制代码
let a = 'Hello World!';

first() {
  console.log('Inside first function');
  second();
  console.log('Again inside first function');
}

second() {
  console.log('Inside second function');
}

first();
console.log('Inside Global Execution Context');

当删除代码在浏览器加载时的执行过程:

  • JavaScript 引擎创建一个 全局执行上下文,并把它压入当前执行栈
  • 当遇到first()函数调用时,JavaScript引擎为该函数创建一个新的执行上下文,并把它压入当前执行栈的顶部
  • first()内部调用second()函数时,JavaScript 引擎为 second() 函数创建了一个新的执行上下文,并把它压入当前执行栈的顶部
  • second() 函数执行完毕,它的执行上下文会从当前栈弹出
  • 控制流程到达下一个执行上下文,即 first() 函数的执行上下文
  • first() 执行完毕,它的执行上下文从栈弹出
  • 控制流程到达全局执行上下文
  • 所有代码执行完毕,JavaScript 引擎从当前栈中移除全局执行上下文。

五、总结

5.1 程序执行过程

  1. 程序启动,全局执行上下文被创建,压入调用栈
    • 创建全局上下文的 词法环境
      • 创建 对象环境记录器 。它用来定义出现在 全局上下文 中的变量和函数的关系(负责处理 letconst 定义的变量)
      • 创建 外部环境引用 。值为 null
    • 创建全局上下文的 变量环境
      • 创建 对象环境记录器 。它持有 变量声明语句 在执行上下文中创建的绑定关系(负责处理 var 定义的变量,初始值为 undefined 造成声明提升)
      • 创建 外部环境引用 。值为 null
    • 确定 this 值为全局对象(以浏览器为例,就是 window
  2. 函数被调用,函数执行上下文被创建,压入调用栈
    • 创建函数上下文的 词法环境
      • 创建 声明式环境记录器 。存储变量、函数和参数,它包含了一个传递给函数的 arguments 对象(此对象存储索引和参数的映射)和传递给函数的参数的 length 。(负责处理 letconst 定义的变量)
      • 创建 外部环境引用,值为全局对象,或者为父级词法环境(作用域)
    • 创建函数上下文的 变量环境
    • 确定this
  3. 进入函数执行上下文的执行阶段
    • 在上下文中运行/解释函数代码,并在代码逐行执行时分配变量值。

5.2 JavaScript的阶段

JavaScript是解释型语言,JavaScript的执行分为解释和执行两个阶段,并且这两个阶段所做的事情不同:

  • 解释阶段
    • 词法分析
    • 语法分析
    • 作用域规则确定
  • 执行阶段
    • 创建执行上下文
    • 执行函数代码
    • 垃圾回收

JavaScript解释阶段便会确定作用域规则,因此作用域在函数定义时就已经确定了 ,而不是在函数调用时确定,但是执行上下文是函数执行之前创建的。执行上下文最明显的就是this的指向是执行时确定的。而作用域访问的变量是编写代码的结构确定的。

作用域和执行上下文之间最大的区别是: 执行上下文在运行时确定,随时可能改变;作用域在定义时就确定,并且不会改变

一个作用域下可能包含若干个上下文环境。有可能从来没有过上下文环境(函数从来就没有被调用过);有可能有过,现在函数被调用完毕后,上下文环境被销毁了;有可能同时存在一个或多个(闭包)。同一个作用域下,不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值

简要概况一下作用域词法环境执行上下文这三者的概念:

  • 作用域:作用域就是一个独立的区域,它可以让变量不会向外暴露出去。作用域最大的用处就是隔离变量。内层作用域可以访问外层作用域。一个作用域下可能包含若干个执行上下文。
  • 词法环境:指相应代码块内标识符与变量值、函数值之间的关联关系的一种体现。词环境内部包含环境记录器和对外部环境的引用。环境记录器是存储变量和函数声明的实际位置,对外部环境的引用意味着可以访问父级词法环境。
  • 执行上下文:JavaScript代码运行的环境。分为全局执行上下文,函数执行上下文和eval函数执行上下文(前两个较常见)。创建执行上下文时会进行this绑定、创建词法环境和变量环境。

资料来源

相关推荐
i听风逝夜12 分钟前
Web 3D地球实时统计访问来源
前端·后端
iMonster16 分钟前
React 组件的组合模式之道 (Composition Pattern)
前端
呐呐呐呐呢24 分钟前
antd渐变色边框按钮
前端
元直数字电路验证43 分钟前
Jakarta EE Web 聊天室技术梳理
前端
wadesir1 小时前
Nginx配置文件CPU优化(从零开始提升Web服务器性能)
服务器·前端·nginx
牧码岛1 小时前
Web前端之canvas实现图片融合与清晰度介绍、合并
前端·javascript·css·html·web·canvas·web前端
灵犀坠1 小时前
前端面试八股复习心得
开发语言·前端·javascript
9***Y481 小时前
前端动画性能优化
前端
网络点点滴1 小时前
Vue3嵌套路由
前端·javascript·vue.js
牧码岛1 小时前
Web前端之Vue+Element打印时输入值没有及时更新dom的问题
前端·javascript·html·web·web前端