深入理解JavaScript 中的执行上下文和执行栈


Posted in Javascript onOctober 23, 2018

如果你是或者想成为一名 JavaScript 开发者,你必须知道 JavaScript 程序内部是如何执行的。理解执行上下文和执行栈对于理解其他 JavaScript 概念(如变量声明提升,作用域和闭包)至关重要。

正确理解执行上下文和执行栈的概念将使您成为更出色的 JavaScript 开发者。

闲话少说,让我们开始吧 :)

分享自 Bit 的博客

使用 Bit 应用所提供的组件作为构建模块,你就是架构师。随时随地和你的团队分享、发现和开发组件,快来尝试鲜!

Bit - 分享和创造代码组件: Bit 能帮助你在不同项目和应用中分享、发现和使用代码组件来创建新功能和……

什么是执行上下文?

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

执行上下文的类型

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

  • 全局执行上下文 — 这是默认或者说基础的上下文,任何不在函数内部的代码都在全局上下文中。它会执行两件事:创建一个全局的 window 对象(浏览器的情况下),并且设置 this 的值等于这个全局对象。一个程序中只会有一个全局执行上下文。
  • 函数执行上下文 — 每当一个函数被调用时, 都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序(将在后文讨论)执行一系列步骤。
  • Eval 函数执行上下文 — 执行在 eval 函数内部的代码也会有它属于自己的执行上下文,但由于 JavaScript 开发者并不经常使用 eval,所以在这里我不会讨论它。

执行栈

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

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

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

让我们通过下面的代码示例来理解:

let 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');

上述代码的执行上下文栈。

深入理解JavaScript 中的执行上下文和执行栈

当上述代码在浏览器加载时,JavaScript 引擎创建了一个全局执行上下文并把它压入当前执行栈。当遇到 first() 函数调用时,JavaScript 引擎为该函数创建一个新的执行上下文并把它压入当前执行栈的顶部。

当从 first() 函数内部调用 second() 函数时,JavaScript 引擎为 second() 函数创建了一个新的执行上下文并把它压入当前执行栈的顶部。当 second() 函数执行完毕,它的执行上下文会从当前栈弹出,并且控制流程到达下一个执行上下文,即 first() 函数的执行上下文。

当 first() 执行完毕,它的执行上下文从栈弹出,控制流程到达全局执行上下文。一旦所有代码执行完毕,JavaScript 引擎从当前栈中移除全局执行上下文。

怎么创建执行上下文?

到现在,我们已经看过 JavaScript 怎样管理执行上下文了,现在让我们了解 JavaScript 引擎是怎样创建执行上下文的。
创建执行上下文有两个阶段:1) 创建阶段 和 2) 执行阶段。

The Creation Phase

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

  • this 值的决定,即我们所熟知的 This 绑定。
  • 创建词法环境组件。
  • 创建变量环境组件。

所以执行上下文在概念上表示如下:

ExecutionContext = {
 ThisBinding = <this value>,
 LexicalEnvironment = { ... },
 VariableEnvironment = { ... },
}

This 绑定:

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

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

let foo = {
 baz: function() {
 console.log(this);
 }
}

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

let bar = foo.baz;

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

词法环境

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

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

简单来说词法环境是一种持有标识符—变量映射的结构。(这里的标识符指的是变量/函数的名字,而变量是对实际对象[包含函数类型对象]或原始数据的引用)。
现在,在词法环境的内部有两个组件:(1) 环境记录器和 (2) 一个外部环境的引用。

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

词法环境有两种类型:

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

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

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

简而言之,

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

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

抽象地讲,词法环境在伪代码中看起来像这样:

GlobalExectionContext = {
 LexicalEnvironment: {
  EnvironmentRecord: {
   Type: "Object",
   // 在这里绑定标识符
  }
  outer: <null>
 }
}

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

变量环境:

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

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

在 ES6 中,词法环境组件和变量环境的一个不同就是前者被用来存储函数声明和变量(let 和 const)绑定,而后者只用来存储 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 >,
   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>
 }
}

注意 — 只有遇到调用函数 multiply 时,函数执行上下文才会被创建。

可能你已经注意到 let 和 const 定义的变量并没有关联任何值,但 var 定义的变量被设成了 undefined。

这是因为在创建阶段时,引擎检查代码找出变量和函数声明,虽然函数声明完全存储在环境中,但是变量最初设置为 undefined(var 情况下),或者未初始化(let 和 const 情况下)。

这就是为什么你可以在声明之前访问 var 定义的变量(虽然是 undefined),但是在声明之前访问 let 和 const 的变量会得到一个引用错误。

这就是我们说的变量声明提升。

执行阶段

这是整篇文章中最简单的部分。在此阶段,完成对所有这些变量的分配,最后执行代码。

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

结论

我们已经讨论过 JavaScript 程序内部是如何执行的。虽然要成为一名卓越的 JavaScript 开发者并不需要学会全部这些概念,但是如果对上面概念能有不错的理解将有助于你更轻松,更深入地理解其他概念,如变量声明提升,作用域和闭包。

就是这样,如果你发现这篇文章有用,请点击 ? 按钮并在下面自由地评论!我很乐意和你讨论 ?。

分享自Bit 的博客

Bit 使得在项目和应用中分享小型组件和模块变得非常简单,使您和您的团队可以更快地构建代码。随时随地和你的团队分享、发现和开发组件,快来尝鲜!

Bit - 分享和创造代码组件: Bit 能帮助你在不同项目和应用中分享、发现和使用代码组件来创建新功能和……

总结

以上所述是小编给大家介绍的JavaScript 中的执行上下文和执行栈 ,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
jquery获取table中的某行全部td的内容方法
Mar 08 Javascript
原生JS操作网页给p元素添加onclick事件及表格隔行变色
Dec 01 Javascript
node.js中的fs.open方法使用说明
Dec 17 Javascript
JavaScript中的console.trace()函数介绍
Dec 29 Javascript
jquery代码实现简单的随机图片瀑布流效果
Apr 20 Javascript
JS函数arguments数组获得实际传参数个数的实现方法
May 28 Javascript
Bootstrap源码解读按钮(5)
Dec 23 Javascript
详解Angular-Cli中引用第三方库
May 21 Javascript
angularjs实现table表格td单元格单击变输入框/可编辑状态示例
Feb 21 Javascript
Vue3.0 响应式系统源码逐行分析讲解
Oct 14 Javascript
浅谈TypeScript的类型保护机制
Feb 23 Javascript
详解element上传组件before-remove钩子问题解决
Apr 08 Javascript
浅谈JavaScript 代码整洁之道
Oct 23 #Javascript
使用jquery Ajax实现上传附件功能
Oct 23 #jQuery
详解如何构建Promise队列实现异步函数顺序执行
Oct 23 #Javascript
jquery实现动态添加附件功能
Oct 23 #jQuery
Vue创建头部组件示例代码详解
Oct 23 #Javascript
JavaScript 对引擎、运行时、调用堆栈的概述理解
Oct 22 #Javascript
每个 JavaScript 工程师都应懂的33个概念
Oct 22 #Javascript
You might like
追忆往昔!浅谈收音机的百年发展历史
2021/03/01 无线电
用PHP实现弹出消息提示框的两种方法
2013/12/17 PHP
php读取纯真ip数据库使用示例
2014/01/26 PHP
Yii入门教程之Yii安装及hello world
2014/11/25 PHP
PHP redis实现超迷你全文检索
2017/03/04 PHP
JavaScript更改class和id的方法
2008/10/10 Javascript
jquery isType() 类型判断代码
2011/02/14 Javascript
ajax中get和post的说明及使用与区别
2012/12/23 Javascript
input链接页面、打开新网页等等的具体实现
2013/12/30 Javascript
JavaScript插件化开发教程 (一)
2015/01/27 Javascript
ECMAScript 5中的属性描述符详解
2015/03/02 Javascript
javascript每日必学之继承
2016/02/23 Javascript
Bootstrap实现圆角、圆形头像和响应式图片
2016/12/14 Javascript
Jquery Easyui日历组件Calender使用详解(23)
2016/12/18 Javascript
基于JQuery及AJAX实现名人名言随机生成器
2017/02/10 Javascript
js实现延迟加载的几种方法
2017/04/24 Javascript
JavaScript中正则表达式判断匹配规则及常用方法
2017/08/03 Javascript
vue安装和使用scss及sass与scss的区别详解
2018/10/15 Javascript
[01:10:24]DOTA2-DPC中国联赛 正赛 VG vs Aster BO3 第一场 2月28日
2021/03/11 DOTA
Mac下Supervisor进程监控管理工具的安装与配置
2014/12/16 Python
举例讲解Python设计模式编程的代理模式与抽象工厂模式
2016/01/16 Python
Python 元类实例解析
2018/04/04 Python
解决Ubuntu pip 安装 mysql-python包出错的问题
2018/06/11 Python
Python unittest 简单实现参数化的方法
2018/11/30 Python
使用python socket分发大文件的实现方法
2019/07/08 Python
解决Jupyter notebook更换主题工具栏被隐藏及添加目录生成插件问题
2020/04/20 Python
python怎么调用自己的函数
2020/07/01 Python
Matplotlib.pyplot 三维绘图的实现示例
2020/07/28 Python
Python并发爬虫常用实现方法解析
2020/11/19 Python
工厂总经理岗位职责
2014/02/07 职场文书
《三亚落日》教学反思
2014/04/26 职场文书
综治宣传月活动总结
2014/04/28 职场文书
年度考核个人总结
2015/03/06 职场文书
大学生各类奖学金申请书
2019/06/24 职场文书
python自动化之如何利用allure生成测试报告
2021/05/02 Python
52条SQL语句教你性能优化
2021/05/25 MySQL