深入理解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判别浏览器种类以及IE版本的几种方法小结
Aug 02 Javascript
jQuery Ajax 仿AjaxPro.Utility.RegisterTypeForAjax辅助方法
Sep 27 Javascript
jquery 操作日期、星期、元素的追加的实现代码
Feb 07 Javascript
JS实现CheckBox复选框全选全不选功能
May 06 Javascript
jQuery网页选项卡插件rTabs用法实例分析
Aug 26 Javascript
Bootstrap创建可折叠的组件
Feb 23 Javascript
关于JS变量和作用域详解
Jul 28 Javascript
JSON对象转化为字符串详解
Aug 11 Javascript
微信小程序如何获取用户信息
Jan 26 Javascript
详解搭建es6+devServer简单开发环境
Sep 25 Javascript
vue添加自定义右键菜单的完整实例
Dec 08 Vue.js
JS中锚点链接点击平滑滚动并自由调整到顶部位置
Feb 06 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
在PHP3中实现SESSION的功能(三)
2006/10/09 PHP
PHP 错误之引号中使用变量
2009/05/04 PHP
如何使用php判断所处服务器操作系统的类型
2013/06/20 PHP
Windows中使用计划任务自动执行PHP程序实例
2014/05/09 PHP
详解php中 === 的使用
2016/10/24 PHP
基于jquery的可多选的下拉列表框
2012/07/20 Javascript
JavaSript中变量的作用域闭包的深入理解
2014/05/12 Javascript
js判断鼠标左、中、右键哪个被点击的方法
2015/01/27 Javascript
javascript事件委托的方式绑定详解
2015/06/10 Javascript
jQuery动态星级评分效果实现方法
2015/08/06 Javascript
JS实现选中当前菜单后高亮显示的导航条效果
2015/10/15 Javascript
基于jQuery实现页面搜索功能
2020/03/26 Javascript
vue实现动态添加数据滚动条自动滚动到底部的示例代码
2018/07/06 Javascript
深入理解Promise.all
2018/08/08 Javascript
vue.js自定义组件directives的实例代码
2018/11/09 Javascript
json字符串对象转换代码实例
2019/09/28 Javascript
javascript设计模式 ? 简单工厂模式原理与应用实例分析
2020/04/09 Javascript
uniapp电商小程序实现订单30分钟倒计时
2020/11/01 Javascript
[01:07]DOTA2次级职业联赛 - Fpb战队宣传片
2014/12/01 DOTA
Python基于win32ui模块创建弹出式菜单示例
2018/05/09 Python
Flask框架配置与调试操作示例
2018/07/23 Python
Python多线程处理实例详解【单进程/多进程】
2019/01/30 Python
Python使用dict.fromkeys()快速生成一个字典示例
2019/04/24 Python
tensorflow入门:TFRecordDataset变长数据的batch读取详解
2020/01/20 Python
python如何爬取动态网站
2020/09/09 Python
CSS3用@font-face实现自定义英文字体
2013/09/23 HTML / CSS
巧用HTML5给按钮背景设计不同的动画简单实例
2016/08/09 HTML / CSS
迎接领导欢迎词
2014/01/11 职场文书
机修工工作职责
2014/02/21 职场文书
高中军训感想800字
2014/02/23 职场文书
对孩子的寄语
2014/04/09 职场文书
社会治安综合治理管理责任书
2014/04/16 职场文书
常务副县长“三严三实”对照检查材料思想汇报
2014/10/05 职场文书
2014年护士工作总结范文
2014/11/11 职场文书
未婚证明格式
2015/06/15 职场文书
2016年领导干部正风肃纪心得体会
2015/10/09 职场文书