• 原文:What is the Execution Context & Stack in JavaScript?
  • git地址:JavaScript中的执行上下文和队列(栈)的关系?
  • 导读:以前总是看到相关文章提到什么变量提升,函数提升啥的,什么函数提升优先级大于变量的,总是知其然,不知其所以然,当面试官拿着同一name,却不断function, 和var赋值,然后让你告诉他每一个阶段该是什么值的时候,拿着啥变量提升和函数提升是解释不通的,至少我不能-_-。David Shariff的这篇文章为我们讲述了其中的原理,让人看了豁然开朗

在这篇文章中,我将深入探讨JavaScript的一个最基本的部分,执行上下文。 在本文结束时,您会更清楚解释器都做了些什么,以至于某些函数、变量在声明它们之前就可以使用,它们的值是如何确定的。

什么是执行上下文?

当代码在JavaScript中运行时,它的执行环境非常重要,并且它们分为以下几类:

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

为了便于理解,本文中执行上下文是指:当前被执行的代码的环境、作用域;接下来让我们看一个执行上下文中包含global、function content的代码:

这里没有什么特别之处,1个global context由紫色边框表示,3个不同的function contexts分别由绿色、蓝色和橙色边框表示。只能有1个global context,可以从程序中的任何其他上下文访问。

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

执行上下文堆栈

浏览器中的JavaScript解释器单线程运行。这就意味着同一时间浏览器只执行一件事,其它的事件在执行队列中排队。下图是单线程队列的抽象视图:

我们已经知道,当浏览器首次加载您的脚本时,它默认进入全局执行上下文(global execution contenrt)。如果在您的全局代码中调用一个函数,程序的顺序流进入被调用的函数,创建一个新函数execution context并将该上下文推送到顶部execution stack(执行队列)。

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

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

代码只调用自身3次,将i的值递增1.每次调用foo函数时,都会创建一个新的执行上下文。一旦执行完成,它就会弹出堆栈并且将控制权交给它下面的上下文,直到再次到达global context(koa2的洋葱图想到了没?)

以下是执行队列的5个关键点:

  • 单线程、
  • 同步执行
  • 全局上下文
  • 无限级的函数上下文
  • 每个函数调用都会创建一个新的执行上下文(execution context),包括对自身的调用(递归)

执行上下文详情

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

  1. 创建阶段 [调用函数时,但在执行任何代码之前]:
  • 创建作用域链。
  • 创建变量(variables),函数(functions )和参数(arguments)
  • 确定"this"。
  1. 激活/执行阶段:
  • var 赋值,(function声明)指向函数,解释/执行代码

可以将每个execution context概念上表示为具有3个属性的对象:

executionContextObj = {'scopeChain': { /* variableObject + all parent execution context's variableObject */ },'variableObject': { /* function arguments / parameters, inner variable and function declarations */ },'this': {}
}

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

这executionContextObj是在调用函数时,但在执行实际函数之前创建的。这是第一阶段:创建阶段。这里,解释器通过扫描传入的参数或arguments、本地函数声明和局部变量声明来创建executionContextObj。这次扫描的结果就变成了executionContextObj.variableObject。

以下是解释器如何解析代码的伪概述:

  1. 遇到函数调用。
  2. 在执行function代码之前,创建执行上下文(execution context)。
  3. 进入创建阶段:

    • 初始化作用域链(Scope Chain)。
    • 创建变量对象(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),creation stage长这样子:

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

正如您所看到的,creation stage定义属性的name,不为它们赋值,但formal arguments / parameters(函数传参,arguments)除外。一旦creation stage完成后,执行流程进入函数体,在函数已经完成执行之后的execution stage如下:

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

提升

在很多JavaScript的资料中都提到了提升,解释变量和函数声明被提升到其作用域的顶部。但是,没有人详细解释为什么会发生这种情况,而在你掌握了关于解释器如何创建activation object后,会很容易理解。示例:

(function() {console.log(typeof foo); // function pointerconsole.log(typeof bar); // undefinedvar foo = 'hello',bar = function() {return 'world';};function foo() {return 'hello';}}());​

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

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

    • 如果我们遵循creation stage,我们知道变量在activation / code execution stage之前就创建了。所以当功能流程开始执行时,foo早就在activation object中定义了。
  • foo是声明了两次,为什么显示foo的是 function ,__不是__ undefined string?

    • 即使foo声明了两次,我们也知道在creation stage函数在变量之前就在activation objectbefore上创建了,如果属性名已经存在于activation object,解释器会忽略掉此次声明。
    • 因此,首先会在activation object上创建一个foo()的引用,当解释器到达时var foo,属性名称foo存在,所以代码什么也不做,然后继续。
  • 为什么 bar 是 undefined?

    • bar实际上是一个具有函数赋值的变量,我们知道变量是在creation stage创建的,但它们的初始值为undefined。

概要

希望到现在您已经很好地掌握了JavaScript解释器如何执行您的代码。理解执行上下文和队列可以让您了解代码没有达到预期的原因

您是否认为了解解释器的内部工作原理是您的JavaScript知识的重要组成部分?知道执行上下文的每个阶段是否有助于您编写更好的JavaScript?

__注意__:有些人一直在问关于闭包,回调,超时等,我将在在下一篇文章中涉及,主要概述作用域链与execution context的关系。

拓展

  • ECMA-262-3 in detail. Chapter 2. Variable object
  • Identifier Resolution, Execution Contexts and scope chains

# JavaScript中的执行上下文和队列(栈)的关系?相关推荐

  1. 了解Javascript中的执行上下文和执行堆栈

    By Sukhjinder Arora | Aug 28, 2018 原文 如果你是或者你想要成为一名js开发者,那么你必须了解js程序内部的运作.理解执行上下文和执行堆栈对于理解js的其它概念(如提 ...

  2. JavaScript中的执行环境

    JavaScript中的执行环境 注意区分函数的执行环境和函数声明环境 看代码: 转载于:https://www.cnblogs.com/mc67/p/5190179.html

  3. JavaScript中立即执行函数实例详解 转载 作者:李牧羊

    javascript和其他编程语言相比比较随意,所以javascript代码中充满各种奇葩的写法,有时雾里看花,当然,能理解各型各色的写法也是对javascript语言特性更进一步的深入理解.这篇文章 ...

  4. Javascript中你必须理解的执行上下文和调用栈

    执行上下文在 JavaScript 是非常重要的基础知识,想要理解 JavaScript 的执行过程,执行上下文是你必须要掌握的知识.否则只能是知其然不知其所以然. 理解执行上下文有什么好处呢? 它可 ...

  5. java函数ao活动对象_JavaScript中的执行上下文和变量对象

    执行上下文(Execution Context) JavaScript代码执行的过程,包括编译和执行两个阶段,编译就是通过词法分析,构建抽象抽象语法树,并编译成机器识别的指令,在JavaScript代 ...

  6. 了解javascript中函数执行顺序

    我个人觉得一般人不会有这种写法,但艺不压身,呵呵.希望能帮到初学的朋友!大家一起进步! 首先列举出8个例子,然后例子的解答会在文章末尾贴出! 测试代码一: <script language=&q ...

  7. javascript中自执行(自调用)函数的两种写法

    自执行函数定义: 自执行函数或是自调用函数 声明完了,马上进行调用,只能使用一次,,有两种写法,举个栗子如下: 写法一: 格式:(函数)(实参) <script>(function (n1 ...

  8. 一文扫清对 JavaScript 中的疑惑

    一文扫清对 JavaScript 中的疑惑 文章目录 一文扫清对 JavaScript 中的疑惑 一.前言 1. 写作原因 : 2. 阅读须知 : 3. 文章声明 : 二.荡平疑惑 1. 我的 JS ...

  9. js中立即执行函数会预编译吗_作为前端你了解JavaScript运行机制吗?

    作为前端工程师,大家都知道js是前端一开始就要学会的知识点,js的代码你会写了,那js的运行机制你了解吗?只有了解了js的运行机制,才能在工作中如鱼得水,今天就跟随珠峰的老师一起来了解下js的运行机制 ...

最新文章

  1. 修改cpu型号重启不变_猫头鹰展示D系列新款140毫米CPU散热器:更大散热片,能压400瓦...
  2. 越秀人民币夹层二期完成首轮关账 首期募集近10亿元...
  3. 华擎b365m itx 黑苹果_迷你ITX小机箱装机实录,黑苹果首选
  4. Matlab某医院用光电比色计,数理统计答案(研究生).ppt
  5. C# 3.0 扩展方法
  6. 人工智障学习笔记——强化学习(3)蒙特卡洛方法
  7. RN学习笔记02:利用WebStorm创建RN项目
  8. 广度优先搜索生成树怎么画_LeetCode0938: 二叉搜索树的范围和
  9. AngularJS过滤器filter-保留小数-渲染页面-小数点-$filter
  10. Windows资源监控工具汇总
  11. java去除空格的函数_JAVA中去掉空格--trim函数
  12. 又一个程序员,被抓捕!(真实事件)
  13. 计算机安全模式无法启动修复,win7系统崩溃无法修复和进入安全模式的解决方法...
  14. angular在IE下报object doesn't support property or method matches问题,以及router-outlet切换时不删除原组件而是添加一个新组件
  15. kubernetes 【组件】ingress 如何通过域名访问您的应用
  16. map、set(底层结构)——C++
  17. 使用dd命令烧写linux系统到sd卡
  18. 边玩边学HTML和CSS
  19. oracle讲座心得1000字,于丹老师的讲座观后感1000字
  20. 【2020-10-26】APP逆向之某某健康(1)

热门文章

  1. 第二天 PYTHON 基本数据类型 - 数字 - 字符串
  2. sort()排序(Comparable、Comparator)
  3. 【WinForm】创建自定义控件(转)
  4. 字符串中最后一个词组的长度 Length of Last Word
  5. PHP实现二维码扫码登录
  6. 命令行刷新Magento索引管理
  7. oracle 学习笔记
  8. Linux学习路径(小白必看)
  9. 顺序三元组 java_三元组顺序结构实现稀疏矩阵相加,行序优先(Java语言描述)
  10. 查看网卡[网络接口]