深入理解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 setCapture 区域外事件捕捉
Mar 18 Javascript
使用jQuery模板来展现json数据的代码
Oct 22 Javascript
jquery each()源代码
Feb 14 Javascript
JS实现新浪微博效果带遮罩层的弹出框代码
Oct 12 Javascript
node.js连接mongoDB数据库 快速搭建自己的web服务
Apr 17 Javascript
Extjs4.0 ComboBox如何实现三级联动
May 11 Javascript
JavaScript 中有关数组对象的方法(详解)
Aug 15 Javascript
Bootstrap栅格系统简单实现代码
Mar 06 Javascript
Angular实现下拉框模糊查询功能示例
Jan 03 Javascript
React Native 图片查看组件的方法
Mar 01 Javascript
在vue中使用axios实现post方式获取二进制流下载文件(实例代码)
Dec 16 Javascript
JavaScript简单编程实例学习
Feb 14 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
探讨:使用XMLSerialize 序列化与反序列化
2013/06/08 PHP
PHP中iconv函数知识汇总
2015/07/02 PHP
PHP正则获取页面所有图片地址
2016/03/23 PHP
PHP 根据key 给二维数组分组
2016/12/09 PHP
PHP基于自增数据如何生成不重复的随机数示例
2017/05/19 PHP
向JavaScript的数组中添加元素的方法小结
2015/10/24 Javascript
实例讲解jQuery EasyUI tree中state属性慎用
2016/04/01 Javascript
全面了解构造函数继承关键apply call
2016/07/26 Javascript
深入浅出讲解ES6的解构
2016/08/03 Javascript
javascript设计模式Constructor(构造器)模式
2016/08/19 Javascript
js阻止冒泡和默认事件(默认行为)详解
2016/10/20 Javascript
JS获得一个对象的所有属性和方法实例
2017/02/21 Javascript
bootstrap表单按回车会自动刷新页面的解决办法
2017/03/08 Javascript
node使用Koa2搭建web项目的方法
2017/10/17 Javascript
详解JavaScript中typeof与instanceof用法
2018/10/24 Javascript
jQuery实现动态加载(按需加载)javascript文件的方法分析
2019/05/31 jQuery
js屏蔽F12审查元素,禁止修改页面代码等实现代码
2020/10/02 Javascript
利用Python脚本在Nginx和uwsgi上部署MoinMoin的教程
2015/05/05 Python
Python中有趣在__call__函数
2015/06/21 Python
python 获取网页编码方式实现代码
2017/03/11 Python
Python操作使用MySQL数据库的实例代码
2017/05/25 Python
django初始化数据库的实例
2018/05/27 Python
Python wxpython模块响应鼠标拖动事件操作示例
2018/08/23 Python
Python判断是否json是否包含一个key的方法
2018/12/31 Python
对python中的try、except、finally 执行顺序详解
2019/02/18 Python
Python实现打包成库供别的模块调用
2020/07/13 Python
浅谈HTML5 &amp; CSS3的新交互特性
2016/07/19 HTML / CSS
使用html5实现表格实现标题合并的实例代码
2019/05/13 HTML / CSS
汽车运用工程专业毕业生推荐信
2013/12/25 职场文书
生日礼品店创业计划书范文
2014/03/21 职场文书
节能环保口号
2014/06/12 职场文书
关爱留守儿童标语
2014/06/18 职场文书
城管执法人员个人对照检查材料思想汇报
2014/09/29 职场文书
《珍珠鸟》教学反思
2016/02/16 职场文书
小学三年级作文之写景
2019/11/05 职场文书
如何利用Python实现一个论文降重工具
2021/07/09 Python