前言

其实规范这东西不是给人看的,它更多的是给语言实现者提供参考。但是当碰到问题找不到答案时,规范往往能提供想要的答案 。偶尔读一下能够带来很大的启发和思考,如果只读一章 Javascript 规范,某位大神觉得非第10章莫属。

我们来试试看,这次选用的是 ECMA2.2的 5.1 版,整个规范才200页, 而第10章共10页,可以感受到 Javascript 的精简,目前的版本加了太多 ES6 的东西,让人望而生畏。

资料地址:http://www.ecma-international...

任务

阅读 ECMA262 5.1 第10章 Executable Code and Execution Contexts (可执行代码与执行上下文)
你能针对这章内容提出问题吗? 即知道答案找出问题。
你能使用图来更形象地表达文章内容吗?

开始我们的探险之旅

原汁原味 ECMAScript 5.1 英文版
平易近人 ECMAScript 5.1 中文版 (有翻译错误,最好是对照英文版一起看 😂 )

可执行代码类型

v8JavaScript 引擎都是按照 ecma-262 的规范来实现的,JavaScript 引擎在解释 JavaScript 代码时,将可执行代码分为了三种。分别是:

  • 全局代码

    代码加载时首先进入的环境。例如加载外部的 `js` 文件或者本地 `<script></script>` 标签内的代码。但不包括任何 `function` 体内的代码。
    
  • 函数代码

    是指作为 `function` 被解析的源代码。不包括作为其嵌套函数的 `function` 被解析的源代码。
    
  • eval代码

    指的是传递给 eval 内置函数的代码。
    

注:不了解 eval(string) 的小伙伴,请参考 eval() - JavaScript | MDN

执行环境 (可执行上下文)

如果我们的 JavaScript 程序有各种函数,函数之间还有嵌套的情况,那怎么 JavaScript 引擎怎么解释各种声明和执行上下文哪?

当控制器转入 ECMA 脚本的可执行代码时,上文已经说了有三种可执行代码,不管进入哪一种控制器都会进入一个执行环境。多个执行环境在逻辑上形成一个栈结构。栈结构最顶层的执行环境称为当前运行的执行环境,最底层是全局执行环境。

用一张图解释

因为 JS 引擎被实现为单线程,也就是同一时间只能发生一件事情,其他的行为就会依次排队。

你可以有任意多个函数执行环境,每次调用函数创建一个新的执行环境,会创建一个私有作用域,函数内部声明的任何变量都不能在当前函数作用域外部直接访问。函数能访问当前执行环境外面的变量声明,但在外部执行环境不能访问内部的变量/函数声明。

关于执行栈(调用栈)总结

单线程。
同步执行。
一个全局上下文。
无限制函数上下文。
每次函数被调用创建新的执行上下文,包括调用自己。
return 或者抛出异常退出一个执行环境。

我们用一个具体的函数理解:

function foo(i) {
  if (i < 0) return;
  console.log('begin:' + i);
  foo(i - 1);
  console.log('end:' + i);
}
foo(2);

// 输出:

// begin:2
// begin:1
// begin:0
// end:0
// end:1
// end:2

代码的执行流程进入内部函数,创建一个新的执行上下文并把它压入执行栈的顶部。浏览器总会执行位于栈顶的执行上下文,一旦当前上下文函数执行结束,它将被从栈顶弹出,并将上下文控制权交给当前的栈。 这样,堆栈中的上下文就会被依次执行并且弹出堆栈,直到回到全局的上下文。

执行环境包含所有用于追踪与其相关的代码的执行进度的状态。精确地说,每个执行环境包含如下表列出的组件。

执行环境的三个状态

组件 作用目的
词法环境 指定一个词法环境对象,用于解析该执行环境内的代码创建的标识符引用。
变量环境 指定一个词法环境对象,其环境数据用于保存由该执行环境内的代码通过 变量表达式 和 函数表达式 创建的绑定。
this 绑定 指定该执行环境内的 ECMA 脚本代码中 this 关键字所关联的值。

现在我们已经知道了每个函数调用都会创建一个新的 执行上下文 。 然而,在 JavaScript 解释器内部,对每个执行上下文的调用会经历两个阶段:

创建阶段 [当函数被调用, 但内部的代码还没开始执行]:
创建 作用域链.
创建变量、函数以及参数
决定 "this"的值
激活 / 代码执行阶段: 
赋值, 寻找函数引用以及解释 /执行代码

解释执行全局代码或使用 eval 函数输入的代码会创建并进入一个新的执行环境。每次调用 ECMA 脚本代码定义的函数也会建立并进入一个新的执行环境,即便函数是自身递归调用的。每一次 return 都会退出一个执行环境。抛出异常也可退出一个或多个执行环境。

当函数没有指明 return 什么的时候,默认 return undefined

当控制流进入一个执行环境时,会设置该执行环境的 this 绑定,定义变量环境和初始词法环境,并执行定义绑定初始化过程。以上这些步骤的严格执行方式由进入的代码的类型决定。比如进入全局代码执行以下步骤:

将变量环境设置为 全局环境 。 
将词法环境设置为 全局环境 。
将 this 绑定设置为 全局对象 。

每个执行环境都有一个关联的变量环境。当在一个执行环境下评估一段 ECMA 脚本时,变量和函数定义会以绑定的形式添加到这个变量环境的环境记录中。

提问

读完这篇文章,问问自己,能够回答下面的问题吗?

1、ECMAScript 中可执行代码有几种?
2、什么情况下会创建一个执行环境?
3、什么情况下会退出一个执行环境?

参考

深入理解JavaScript系列(11):执行上下文(Execution Contexts)
了解JavaScript的执行上下文
深入理解JavaScript执行上下文、函数堆栈、提升的概念

推荐阅读

JavaScript欲速则不达—通过解析过程了解JavaScript

如果觉得我的文章对你有用,请随意赞赏

载入中...
小猿大圣 小猿大圣

716 声望

发布于专栏

小猿大圣的技术之路

小猿变大圣,小工到专家,记录技术点滴。

0 人关注