从 ECMAScript 认识 JS(序章):我们为什么要学习 ECMAScript 规范

1,554 阅读11分钟

本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!

写在前面

大家好,我是早晚会起风。这是专栏《从 ECMAScript 语言规范和浏览器引擎的视角认识 JavaScript》的第一篇文章。

正如专栏名称,我会带着大家从 ECMAScript 和浏览器引擎的视角来深入了解 JavaScript 这门语言。

作为前端开发者,我相信大家或多或少都经历过面试,为了面试不止一次地巩固 JavaScript 基础。很有可能,你在某一刻对这门语言里某一些奇怪的特性感到难以理解,忍不住产生两大疑问😕——“为什么这样可以?” 和 “为什么那样不行?”。

然后,有一些同学了解到 ECMAScript 语言规范能够解决自己的疑问。于是你开始不止一次地想要尝试阅读 ECMAScript 规范,加深对 JavaScript 的理解。但每次打开规范文档后都会只能看着满屏的英文,最后束手无策。我写这篇专栏的初衷就是希望尽自己的绵薄之力帮你推开这扇大门,让你更加轻松地阅读 ECMAScript 规范。

本篇专栏会以 ECMAScript 语言规范为主线,浏览器引擎部分只会作为辅助。我并不打算深入讲解浏览器引擎,主要是我的能力实在有限,没办法兼顾。专栏中的一些内容如果结合浏览器引擎的视角可能会让大家理解得更加清楚,所以在有需要的部分(比如作用域与闭包),我会穿插讲述一些浏览器部分的内容。

这门专栏不会以面试(面试题)为导向。我不希望这个系列的文章变成“八股文”。当你对 JavaScript 的特性产生疑问,对它背后的原理产生兴趣的时候,你才需要阅读规范。如果只是为了面试,阅读规范大概率是一件费力不讨好的事情。我更希望做到授人以渔,和大家一起有所成长。

需要注意的是,这篇专栏不适合刚入门前端的同学阅读。建议有一定基础后再来读,这个时候你对 JavaScript 中的一些奇奇怪怪的特性也会感同身受。

另外,ECMAScript 语言规范的内容一直在变化,专栏里引用的规范内容总有一天会过时。不过不需要担心,当我们掌握方法之后,这些都不是问题。

本篇专栏开始于 2022年09月18日,专栏文章中引用的规范内容均处于这段时间,读者如果发现最新的规范与文中规范不一致,均以最新规范为主。

附规范地址:tc39.es/ecma262/

说这么多,我们开始正题吧~

为什么要阅读 ECMAScript 规范

相信有很多同学在对 JavaScript 理解更深的同时,心里的疑惑也愈来愈多。我们简单举几个例子,

那些奇怪的特性

例子一:typeof 操作符

const foo = {};
typeof foo === 'object' // true

function bar () {};
typeof bar === 'function' // true

为什么 typeof 操作符可以区分 objectfunction 类型?要知道 JavaScript 中的函数也是对象。但是对于其它类型的对象,比如 ArrayDate 它又无法区分。

通常我们会使用 Object.prototype.toString.call() 来区分不同类型的对象,那为什么这种方式就可行呢? toString() 时发生了什么?

例子二:原型链查找

const person2 = { name: '气疯', get myName() { return this.name } }
const person1 = { name: '起风' }
Object.setPrototypeOf(person1, person2);
console.log(person1.myName); // 起风

在这个例子中,我们为 person2 定义了 get myName 这个属性,并将其设置为 person1 的原型对象。

但是当我们打印 person1.myName 时,拿到的却是 person1 上定义的 name 属性。那么问题来了,为什么 person2 中的函数属性内的 this 会指向 person1 对象。

例子三:隐式类型转换

[] + null + 1 // null1
null == "" // false

在我们使用 JavaScript 的过程中,不可避免地会涉及到隐式类型转换过程。隐式类型转换繁琐的规则让我们苦不堪言,但我们还必须了解它,因为不论是工作还是面试,我们总会见到它。

很多同学(包括我在内)都会通过刷面试题或者死记硬背的方式来学习这些规则,但是问题是我们虽然能在一段时间内记住,但是过段时间总是会忘记。比忘记更可怕的是,因为记忆的不准确,我们经常混淆这些规则。

除了上述几个例子,我们还可以列举出更多,比如

  • 相等(== )操作符是如何工作的
  • delete foo.bar 是如何在对象上删除一个属性的
  • 比如 this 的原理到底是什么(这部分我之前写过一篇文章介绍,但是现在看还是有些杂乱,后面可能会重新梳理)

这些问题只要从 ECMAScript 语言规范的角度去重新认识,就会迎刃而解。

ECMAScript 是什么

说了这么多, ECMAScript 到底是什么?为什么它就能够解释我们在 JavaScript 中遇到的一切东西呢?它当真有如此大的魔力吗?

我们这里说的 ECMAScript 全称是 ECMAScript Language Specification。通过名称,我们可以知道它是 JavaScript 这门语言的规范和标准。它描述了这门语言的语法和行为。

JavaScript 引擎的开发者需要依据这个规范来确保新特性能够按预期运行。可见,ECMAScript 语言规范就是我们学习 JavaScript 工作原理的最权威的资料,这里没有模棱两可,我们学到的就是最准确的。

ECMAScript 规范由 TC39(Ecma International Technical Committee 39) 团队撰写,内容存放在 tc39.es/ecma262/。TC39 团队每年都会挑选一个时间依据最新的规范生成一个快照作为当年的语言标准版本(ECMAScript Language Standard),比如我们最耳熟能详的 ES6,即 ES2015 。所以,最新的规范都在 TC39 上,不过随着时间流逝,总会有特性被修改或者删除,如果你想了解旧版规范,可能还需要找到对应的快照版本。

官方文档开头的 Introduce 部分也为我们解释了 ECMAScript 的来历,

ECMAScript is based on several originating technologies, the most well-known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company's Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0.

The development of the ECMAScript Language Specification started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997.

以下是翻译:

ECMAScript 基于多种原始技术,最著名的是 JavaScript (Netscape) 和 JScript (Microsoft)。 该语言由 Netscape 的 Brendan Eich 发明,并首次出现在该公司的 Navigator 2.0 浏览器中。 它出现在 Netscape 的所有后续浏览器中,以及从 Internet Explorer 3.0 开始的所有 Microsoft 浏览器中。

ECMAScript 语言规范的开发始于 1996 年 11 月。该 Ecma 标准的第一版由 1997 年 6 月的 Ecma 大会通过。

哪些内容属于 ECMAScript 规范

一句话讲,ECMAScript 只定义了语言特性,其它部分与它无关。

《How to Read the ECMAScript Specification》这篇文章中,作者给出了一份列表来展示哪些部分属于 ECMAScript 规范,哪些部分不是。

image

上述表格中,在右侧打 ✔ 表示对应内容属于规范,✘ 表示不属于规范的内容。

✘[2] 中的 API 是浏览器和 Node 端共有的,但它们的定义是分开的。Node中这些 API 在 Node 文档 中定义。而在浏览器中, console 对象由 Console Standard 定义,而 setTimeout() setInterval() 这些 API 在 HTML Standard 中定义(✘[5] 这部分内容是浏览器独有的,也在 HTML Standard 中定义)。

✘[3] 和 ✘[4] 这部分内容是 Node.js 独有的,像 module exports 这些内容我们就很熟悉了。

比较特殊的是❓[1] 中的 import a from ‘a’; 。ECMAScript 规范定义了这个语法,以及它的含义。而模块具体如何被加载的部分,不属于规范内容。

如何阅读 ECMAScript 规范

如果你曾经打开过规范文档,你肯定会有一刻感觉到望而生畏,毕竟它的目录长下边这样。

image

文档不仅内容庞杂,而且充斥着大量的专业术语和概念,更别提是纯英文的了。如果你想像阅读一本书一样,从头开始看起,我劝你耗子尾汁。因为不出一周,你肯定再也没有打开文档的欲望了。

还好,有前人给了我们一些指引,

Personally, I like to divide the spec into five parts:

  • Conventions and basics ("what is a Number? what does it mean when the spec says 'throw a TypeErrorexception'?")
  • Grammar productions of the language ("how does one write a for-in loop?")
  • Static semantics of the language ("how are variable names determined in a var statement?")
  • Runtime semantics of the language ("how is a for-in loop executed?")
  • APIs ("what does String.prototype.substring() do?")

以上内容引自:How to Read the ECMAScript Specification

作者将规范归类为五个部分,我大胆翻译一下(如有描述不准确的地方,还请大家及时指出),

  • 约定和基础(例如:什么是 Number ?TypeError 错误表示什么意思?)
  • 语法的产生(例如:我们要怎么写出一个for-in 循环,即语法规范)
  • 静态语义(例如: var 声明是怎么定义变量的)
  • 运行时语义(例如:for -in 循环的执行过程)
  • APIs(例如:String.prototype.substring() 做了什么)

需要注意的是,这只是作者本人的归类,这样分类有助于我们梳理归纳规范内容。规范本身不是这样组织的,各类定义是在不同章节中交叉进行的。

对此,作者举例如下,

image

例子中具体描述的内容我们现在还不需要理解,作者想要告诉我们的是,规范中的内容分散在各处,相互关联。下面我直接引用作者原话,比我自己表述会更加清晰。

At this point, I’d like to point out that absolutely no one reads the spec from top to bottom. Rather, only look at the section corresponding to what you are trying to look for, and in that section only look at what you need. Try to determine which one of the five big sections your specific question relates to; and if you are having trouble determining which one it is, ask yourself the question "at which time is this (whatever you are trying to confirm) evaluated?"

翻译: 我想指出,没有人会从头到尾阅读规范。 你只需要查看与你想要查找的内容对应的部分,并且在该部分中查看需要的内容。 尝试确定你的具体问题与五个主要部分中的哪一个相关,如果无法确认,请问自己 “这是在什么时候(无论您要确认什么)执行的?”

从我们自己感兴趣的问题,或者说存在疑惑的地方作为切入去阅读规范是一种很好的方式。在这个过程中,你始终有一个想要解决的问题,你可以围绕着这个部分,逐渐探索、扩展对应的内容,总有一个时刻,你会完全解决掉这个问题。

以我自己为例,我之前写过的那篇 ECMAScript 规范解读文章就是以 this 作为切入点,来串联规范中相关联的部分,最后找到了 this 背后的运行原理。这样做效率会更高,并且在过程中随着理解程度的加深,你的成就感会更高,更容易阅读下去。

但是在这之前,ECMAScript 中有大量的专业词汇和术语。如果你对这些完全不理解,你甚至可能都找不到“线头”去把这些内容串联起来。本篇专栏的首要目的,就是要把这些拦在你面前的大山一一挪走。

最后

这篇文章只是序章,写到现在我们还没正式开始进入 ECMAScript 规范的世界。不过不要着急,从下篇开始,我会逐步开始介绍文档中一些最基础的概念和术语。另外,我强烈推荐大家阅读How to Read the ECMAScript Specification这篇文章,文中介绍的概念也都是我们之后要涉及到的。

为什么要阅读 ECMAScript 规范这一小节,我举了几个有趣的 JavaScript 特性。如果你也有一些好玩的问题,欢迎留言到评论区,之后我可能会摘取一两个案例来深入解读。

最后,欢迎大家一键三连,有大家的支持才有更新的动力嘛~

系列文章见专栏 从 ECMAScript 语言规范和浏览器引擎的视角认识 JavaScript