简单了解JavaScript中的执行上下文和堆栈


Posted in Javascript onJune 24, 2019

什么是执行上下文?

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个关键要点:

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

执行上下文的细节

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

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

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

2.激活/代码执行阶段:

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

可以将每个执行上下文在概念上表示为具有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 相关文章推荐
jQuery插件pagination实现分页特效
Apr 12 Javascript
jQuery实现MSN中文网滑动Tab菜单效果代码
Sep 09 Javascript
Perl Substr()函数及函数的应用
Dec 16 Javascript
jQuery基本过滤选择器用法示例
Sep 09 Javascript
JavaScript数据结构之二叉树的计数算法示例
Apr 13 Javascript
详解Javascript获取缓存和清除缓存API
May 25 Javascript
如何使用JS在HTML中自定义字符串格式化
Jul 20 Javascript
vuex state及mapState的基础用法详解
Apr 19 Javascript
小程序兼容安卓和IOS数据处理问题及坑
Sep 18 Javascript
layui radio单选限制下一个radio单选的实例
Sep 03 Javascript
vue解决使用$http获取数据时报错的问题
Oct 30 Javascript
微信小程序:报错(in promise) MiniProgramError
Oct 30 Javascript
一次让你了解全部JavaScript的作用域
Jun 24 #Javascript
通过循环优化 JavaScript 程序
Jun 24 #Javascript
在React中写一个Animation组件为组件进入和离开加上动画/过度效果
Jun 24 #Javascript
node中实现删除目录的几种方法
Jun 24 #Javascript
什么时候不能在 Node.js 中使用 Lock Files
Jun 24 #Javascript
vue-cli脚手架引入弹出层layer插件的几种方法
Jun 24 #Javascript
浅谈一个webpack构建速度优化误区
Jun 24 #Javascript
You might like
JAVA/JSP学习系列之六
2006/10/09 PHP
php实现图片缩放功能类
2013/12/18 PHP
基于递归实现的php树形菜单代码
2014/11/19 PHP
php jsonp单引号转义
2014/11/23 PHP
PHP信号处理机制的操作代码讲解
2019/04/19 PHP
jQuery学习笔记之jQuery的动画
2010/12/22 Javascript
讨论html与javascript在浏览器中的加载顺序问题
2013/11/27 Javascript
jQuery的css() 方法使用指南
2015/05/03 Javascript
JavaScript前端开发之实现二进制读写操作
2015/11/04 Javascript
AngularJS 视图详解及示例代码
2016/08/17 Javascript
Javascript从数组中随机取出不同元素的两种方法
2016/09/22 Javascript
Jquery和Js获得元素标签名称的方法总结
2016/10/08 Javascript
使用Vue-Router 2实现路由功能实例详解
2017/11/14 Javascript
Vue中v-show添加表达式的问题(判断是否显示)
2018/03/26 Javascript
vue 中动态绑定class 和 style的方法代码详解
2018/06/01 Javascript
JavaScript中构造函数与原型链之间的关系详解
2019/02/25 Javascript
vue中利用Promise封装jsonp并调取数据
2019/06/18 Javascript
antd日期选择器禁止选择当天之前的时间操作
2020/10/29 Javascript
vue使用keep-alive实现组件切换时保存原组件数据方法
2020/10/30 Javascript
vue登录页实现使用cookie记住7天密码功能的方法
2021/02/18 Vue.js
vue3.0中使用element的完整步骤
2021/03/04 Vue.js
简单介绍Python中的RSS处理
2015/04/13 Python
Python文档生成工具pydoc使用介绍
2015/06/02 Python
python生成器表达式和列表解析
2016/03/10 Python
详解python中TCP协议中的粘包问题
2019/03/22 Python
Python自动生成代码 使用tkinter图形化操作并生成代码框架
2019/09/18 Python
python递归函数求n的阶乘,优缺点及递归次数设置方式
2020/04/02 Python
pytorch使用horovod多gpu训练的实现
2020/09/09 Python
python 如何将office文件转换为PDF
2020/09/22 Python
python3爬虫中多线程进行解锁操作实例
2020/11/25 Python
HTML5探秘:用requestAnimationFrame优化Web动画
2018/06/03 HTML / CSS
幼儿师范毕业生自荐信
2013/11/09 职场文书
司马光教学反思
2014/02/01 职场文书
大学生志愿者心得体会
2016/01/15 职场文书
Ajax异步刷新功能及简单案例
2021/11/20 Javascript
对象析构函数__del__在Python中何时使用
2022/03/22 Python