深入探讨JavaScript的最基本部分之执行上下文


Posted in Javascript onFebruary 12, 2019

在这篇文章中,我将深入探讨JavaScript的最基本部分之一,即Execution Context(执行上下文)。 在本文结束时,你应该对解释器了解得更清楚:为什么在声明它们之前可以使用某些函数或变量?以及它们的值是如何确定的?

什么是执行上下文?

JavaScript的执行环境非常重要,当JavaScript代码在行时,会被预处理为以下情况之一:

  • Global code - 首次执行代码的默认环境。
  • Function code - 每当执行流程进入函数体时。
  • Eval code - 要在eval函数内执行的文本。

你可以阅读大量涉及作用域的在线资料,不过为了使事情更容易理解,让我们将术语“执行上下文”视为当前代码的运行环境或作用域。接下来让我们看一个包含global和function / local上下文的代码示例。

深入探讨JavaScript的最基本部分之执行上下文

这里没有什么特别之处,我们有一个由紫色边框表示的全局上下文,和由绿色,蓝色和橙色边框表示的3个不同的函数上下文。 只能有1个全局上下文,可以从程序中的任何其他上下文访问。

你可以拥有任意数量的函数上下文,并且每个函数调用都会创建一个新的上下文,从而创建一个私有作用域,其中无法从当前函数作用域外直接访问函数内部声明的任何内容。 在上面的示例中,函数可以访问在其当前上下文之外声明的变量,但外部上下文无法访问在其中声明的变量或函数。 为什么会这样呢? 这段代码究竟是如何处理的?

Execution Context Stack(执行上下文堆栈)

浏览器中的JavaScript解释器被实现为单个线程。 实际上这意味着在浏览器中一次只能做一件事,其他动作或事件在所谓的执行堆栈中排队。 下图是单线程堆栈的抽象视图:

深入探讨JavaScript的最基本部分之执行上下文

我们已经知道,当浏览器首次加载脚本时,它默认进入全局上下文执行。 如果在全局代码中调用函数,程序的顺序流进入被调用的函数,创建新的执行上下文并将其推送到执行堆栈的顶部。

如果在当前函数中调用另一个函数,则会发生同样的事情。 代码的执行流程进入内部函数,该函数创建一个新的执行上下文,该上下文被推送到现有堆栈的顶部。 浏览器将始终执行位于堆栈顶部的当前执行上下文,并且一旦函数执行完当前执行上下文后,它将从栈顶部弹出,把控制权返回到当前栈中的下一个上下文。 下面的示例显示了递归函数和程序的执行堆栈:

(function foo(i) {
  if (i === 3) {
    return;
  }
  else {
    foo(++i);
  }
}(0));

深入探讨JavaScript的最基本部分之执行上下文

代码简单地调用自身3次,并将i的值递增1。每次调用函数foo时,都会创建一个新的执行上下文。 一旦上下文完成执行,它就会弹出堆栈并且讲控制返回到它下面的上下文,直到再次达到全局上下文。

关于执行堆栈execution stack有5个关键要点:

  1. 单线程。
  2. 同步执行。
  3. 一个全局上下文。
  4. 任意多个函数上下文。
  5. 每个函数调用都会创建一个新的执行上下文execution context,甚至是对自身的调用。

执行上下文的细节

所以我们现在知道每次调用一个函数时,都会创建一个新的执行上下文。 但是,在JavaScript解释器中,对执行上下文的每次调用都有两个阶段:

创建阶段 [调用函数时,但在执行任何代码之前]:

  1. 创建作用域链。
  2. 创建变量,函数和参数。
  3. 确定“this”的值。

激活/代码执行阶段:

  • 分配值,引用函数和解释/执行代码。

可以将每个执行上下文在概念上表示为具有3个属性的对象:

executionContextObj = {
  'scopeChain': { /* variableObject + 所有父执行上下文的variableObject */ },
  'variableObject': { /* 函数实参/形参,内部变量和函数声明 */ },
  'this': {}
}

激活对象/变量对象 [AO/VO]

在调用该函数,并且在实际执行函数之前,会创建这个executionContextObj。 这被称为第1阶段,即创造阶段。 这时解释器通过扫描函数传递的实参或形参、本地函数声明和局部变量声明来创建executionContextObj。 此扫描的结果将成为executionContextObj中的variableObject。

以下是解释器如何预处理代码的伪代码概述:

1.找一些代码来调用一个函数。

2.在执行功能代码之前,创建执行上下文。

3.进入创建阶段:

    ①初始化作用域链。

    ②创建variable object:

  • 创建arguments object,检查参数的上下文,初始化名称和值并创建引用副本。
  • 扫描上下文以获取函数声明:
  • 对于找到的每个函数,在variable object中创建一个属性,该属性是函数的确切名称,该属性存在指向内存中函数的引用指针。
  • 如果函数名已存在,则将覆盖引用指针值。
  • 扫描上下文以获取变量声明:
  • 对于找到的每个变量声明,在variable object中创建一个属性作为变量名称,并将该值初始化为undefined。
  • 如果变量名称已存在于variable object中,则不执行任何操作并继续扫描。

    ③确定上下文中“this”的值。

4.激活/执行阶段:

  • 在上下文中运行/解释函数代码,并在代码逐行执行时分配变量值。

我们来看一个例子:

function foo(i) {
  var a = 'hello';
  var b = function privateB() {
  };
  function c() {
  }
}
foo(22);

在调用foo(22)时,创建阶段如下所示:

fooExecutionContext = {
  scopeChain: { ... },
  variableObject: {
    arguments: {
      0: 22,
      length: 1
    },
    i: 22,
    c: pointer to function c()
    a: undefined,
    b: undefined
  },
  this: { ... }
}

如你所见,创建阶段处理定义属性的名称,而不是为它们赋值,但正式的形参/实参除外。创建阶段完成后,执行流程进入函数,激活/代码执行阶段在函数执行完毕后如下所示:

fooExecutionContext = {
  scopeChain: { ... },
  variableObject: {
    arguments: {
      0: 22,
      length: 1
    },
    i: 22,
    c: pointer to function c()
    a: 'hello',
    b: pointer to function privateB()
  },
  this: { ... }
}

关于hoisting

你可以找到许多使用JavaScript定义术语hoisting的在线资源,解释变量和函数声明被hoisting到其函数范围的顶部。 但是没有人能够详细解释为什么会发生这种情况,掌握了关于解释器如何创建激活对象的新知识,很容易理解为什么。 请看下面的代码示例:

(function() {
  console.log(typeof foo); // function pointer
  console.log(typeof bar); // undefined
  var foo = 'hello',
    bar = function() {
      return 'world';
    };
  function foo() {
    return 'hello';
  }
}());

我们现在可以回答的问题是:

为什么我们可以在声明foo之前就能访问?

  • 如果我们理解了创建阶段,就知道在激活/代码执行阶段之前已经创建了变量。因此,当函数流开始执行时,已经在激活对象中定义了foo。

Foo被声明两次,为什么foo显示为function而不是undefined或string?

  • 即使foo被声明两次,我们通过创建阶段知道函数在变量之前就被创建在激活对象上了,而且如果激活对象上已经存在了属性名称,我们只是绕过了声明这一步骤。
  • 因此,首先在激活对象上创建对函数foo()的引用,并且当解释器到达var foo时,我们已经看到属性名称foo存在,因此代码不执行任何操作并继续处理。

为什么bar未定义?

  • bar实际上是一个具有函数赋值的变量,我们知道变量是在创建阶段被创建的,但它们是使用undefined值初始化的。

希望到这里你已经能够很好地掌握了JavaScript解释器如何预处理你的代码。 理解执行上下文和堆栈可以让你了解背后的原因:为什么代码预处理后的值和你预期的不一样。

你认为学习解释器的内部工作原理是多此一举还是非常必要的呢? 了解执行上下文阶段是否能够帮你你写出更好的JavaScript呢?

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。如果你想了解更多相关内容请查看下面相关链接

Javascript 相关文章推荐
[转]JS宝典学习笔记
Feb 07 Javascript
使用JS或jQuery模拟鼠标点击a标签事件代码
Mar 10 Javascript
使用GruntJS构建Web程序之安装篇
Jun 04 Javascript
AngularJS实现全选反选功能
Dec 08 Javascript
基于javascript bootstrap实现生日日期联动选择
Apr 07 Javascript
js当前页面登录注册框,固定div,底层阴影的实例代码
Oct 04 Javascript
Js实现中国公民身份证号码有效性验证实例代码
May 03 Javascript
vue.js删除动态绑定的radio的指定项
Jun 02 Javascript
Angularjs 手写日历的实现代码(不用插件)
Oct 18 Javascript
VueJs使用Amaze ui调整列表和内容页面
Nov 30 Javascript
JavaScript实现异步图像上传功能
Jul 12 Javascript
js实现掷骰子小游戏
Oct 24 Javascript
图文详解vue框架安装步骤
Feb 12 #Javascript
详解vue组件中使用路由方法
Feb 12 #Javascript
说说Vue.js中的functional函数化组件的使用
Feb 12 #Javascript
详解Vue用cmd创建项目
Feb 12 #Javascript
谈谈JavaScript中super(props)的重要性
Feb 12 #Javascript
微信小程序实现的自定义分享功能示例
Feb 12 #Javascript
图文讲解用vue-cli脚手架创建vue项目步骤
Feb 12 #Javascript
You might like
Windows下PHP的任意文件执行漏洞
2006/10/09 PHP
php实现猴子选大王问题算法实例
2015/04/20 PHP
PHP解密Unicode及Escape加密字符串
2015/05/17 PHP
jQuery Study Notes学习笔记 (二)
2010/08/04 Javascript
js返回上一页并刷新代码整理
2012/12/21 Javascript
关于jquery.validate1.9.0前台验证的使用介绍
2013/04/26 Javascript
深入理解javascript动态插入技术
2013/11/12 Javascript
Jquery 点击按钮自动高亮实现原理及代码
2014/04/25 Javascript
JavaScript判断页面加载完之后再执行预定函数的技巧
2016/05/17 Javascript
Dojo获取下拉框的文本和值实例代码
2016/05/27 Javascript
解析JavaScript中的字符串类型与字符编码支持
2016/06/24 Javascript
WEB 前端开发中防治重复提交的实现方法
2016/10/26 Javascript
js仿拉勾网首页穿墙广告效果
2017/03/08 Javascript
详解angular用$sce服务来过滤HTML标签
2017/04/11 Javascript
vue 中滚动条始终定位在底部的方法
2018/09/03 Javascript
jQuery实现鼠标滑动切换图片
2020/05/27 jQuery
微信小程序连接服务器展示MQTT数据信息的实现
2020/07/14 Javascript
vue Treeselect下拉树只能选择第N级元素实现代码
2020/08/31 Javascript
IDEA配置jQuery, $符号不再显示黄色波浪线的问题
2020/10/09 jQuery
在Python下尝试多线程编程
2015/04/28 Python
利用Python自带PIL库扩展图片大小给图片加文字描述的方法示例
2017/08/08 Python
Python制作豆瓣图片的爬虫
2017/12/28 Python
详解用TensorFlow实现逻辑回归算法
2018/05/02 Python
python实现列表的排序方法分享
2019/07/01 Python
HTML5 Canvas锯齿图代码实例
2014/04/10 HTML / CSS
欧洲领先的电子和电信零售商和服务提供商:Currys PC World Business
2017/12/05 全球购物
车间主管岗位职责
2013/11/14 职场文书
找工作最新求职信
2013/12/22 职场文书
篝火晚会主持词
2014/03/25 职场文书
幼儿园中班评语大全
2014/04/17 职场文书
爱牙日活动总结
2014/08/29 职场文书
中级会计大学生职业生涯规划书
2014/09/16 职场文书
学生检讨书怎么写?
2014/10/10 职场文书
博士给导师的自荐信
2015/03/06 职场文书
敬老院活动感想
2015/08/07 职场文书
2019大学生预备党员转正思想汇报
2019/06/21 职场文书