深入理解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 相关文章推荐
js图片延迟技术一般的思路与示例
Mar 20 Javascript
Javascript中的关键字和保留字整理
Oct 16 Javascript
使用javascript实现雪花飘落的效果
Jan 13 Javascript
jQuery中on()方法用法实例
Jan 19 Javascript
js实现的Easy Tabs选项卡用法实例
Sep 06 Javascript
Node.js程序中的本地文件操作用法小结
Mar 06 Javascript
jQuery滚动监听实现商城楼梯式导航效果
Mar 06 Javascript
关于vue中的ajax请求和axios包问题
Apr 19 Javascript
微信小程序仿美团城市选择
Jun 06 Javascript
JS实现的贪吃蛇游戏完整实例
Jan 18 Javascript
使用webpack编译es6代码的方法步骤
Apr 28 Javascript
如何写好一个vue组件,老夫的一年经验全在这了(推荐)
May 18 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
用PHP代码在网页上生成图片
2015/07/01 PHP
PHP封装curl的调用接口及常用函数详解
2018/05/31 PHP
两个listbox实现选项的添加删除和搜索
2013/03/01 Javascript
Highcharts 非常实用的Javascript统计图demo示例
2013/07/03 Javascript
JS将数字转换成三位逗号分隔的样式(示例代码)
2014/02/19 Javascript
JavaScript实现twitter puddles算法实例
2014/12/06 Javascript
JavaScript中匿名函数用法实例
2015/03/23 Javascript
JS模拟酷狗音乐播放器收缩折叠关闭效果代码
2015/10/29 Javascript
谈谈js中的prototype及prototype属性解释和常用方法
2015/11/25 Javascript
jQuery基于ajax()使用serialize()提交form数据的方法
2015/12/08 Javascript
JS事件添加和移出的兼容写法示例
2016/06/20 Javascript
JQuery遍历元素的后代和同胞实现方法
2016/09/18 Javascript
使用JavaScriptCore实现OC和JS交互详解
2017/03/28 Javascript
纯js代码生成可搜索选择下拉列表的实例
2018/01/11 Javascript
浅谈Webpack打包优化技巧
2018/06/12 Javascript
JavaScript中创建原子的方法总结
2018/08/26 Javascript
JavaScript实现的九种排序算法
2019/03/04 Javascript
vue防止花括号{{}}闪烁v-text和v-html、v-cloak用法示例
2019/03/13 Javascript
CentOS 7下安装Python3.6 及遇到的问题小结
2018/11/08 Python
python re正则匹配网页中图片url地址的方法
2018/12/20 Python
Django框架自定义session处理操作示例
2019/05/27 Python
Jacobi迭代算法的Python实现详解
2019/06/29 Python
Python制作微信好友背景墙教程(附完整代码)
2019/07/17 Python
TensorFlow绘制loss/accuracy曲线的实例
2020/01/21 Python
python实现杨辉三角的几种方法代码实例
2021/03/02 Python
Priority Pass机场贵宾室会籍计划:全球超过1200间机场贵宾室
2018/08/26 全球购物
NYX Professional Makeup俄罗斯官网:世界知名的化妆品品牌
2019/12/26 全球购物
网络工程师自荐书范文
2014/04/01 职场文书
遵纪守法演讲稿
2014/05/23 职场文书
关于国庆节的演讲稿
2014/09/05 职场文书
公安派出所所长四风问题个人对照检查材料
2014/10/04 职场文书
大学生上课迟到检讨书
2014/10/15 职场文书
2014年招商工作总结
2014/11/22 职场文书
诚信承诺书
2015/01/19 职场文书
公司管理制度范本
2015/08/03 职场文书
导游词之淮安明祖陵
2019/11/25 职场文书