理解 ECMAScript 规范(一)
- 原文地址:Understanding the ECMAScript spec, part 1
- 原文作者:marjakh
- 译文出自:IDuxFE 技术文章翻译
- 本文永久链接:github.com/IDuxFE/week…
- 译者:TaylenaWu
发布于 2020 年 2 月 3 日,标签 ECMAScript 理解 ECMAScript
在这篇文章中,我们会从规范中找一个简单的功能,并试着理解其中的符号。让我们开始吧!
前言 #
即使你了解 JavaScript ,阅读它的语言规范 ECMAScript Language specification, or the ECMAScript spec for short 可能也是令人望而生畏的。至少这是我第一次开始阅读时的感受。
让我们以一个具体例子作为开始,然后通过规范去理解它。下面的代码演示了 Object.prototype.hasOwnProperty
的使用:
const o = { foo: 1 };o.hasOwnProperty('foo'); // trueo.hasOwnProperty('bar'); // false
在这个例子中,o
并没有一个 hasOwnProperty
属性,所以我们通过原型链寻找。我们发现它在 o
的原型对象,也就是 Object.prototype
上。
为了描述 Object.prototype.hasOwnProperty
是怎样生效的,规范使用了类似伪代码的说明:
Object.prototype.hasOwnProperty(V)
当用参数
V
调用hasOwnProperty
方法时,以下步骤将会执行:
- 令
P
为? ToPropertyKey(V)
;- 令
O
为? ToObject(this value)
;- 返回
? HasOwnProperty(O, P)
。
以及
抽象操作
HasOwnProperty
被用于确定是否一个对象拥有一个带有特定键的属性。会返回一个布尔值。这个操作以参数O
和P
调用,O
是对象,P
是属性键。这个抽象操作执行以下步骤:
- 断言:
Type(O)
为Object
;- 断言:
IsPropertyKey(P)
为true
;- 令
desc
为? O.[[GetOwnProperty]](P)
;- 若
desc
为undefined
, 返回false
;- 返回
true
。
但是什么是“抽象操作”呢?在 [[ ]]
中的又是什么东西?为什么在函数前面有一个 ?
?断言又是什么意思?
让我们找出答案吧!
语言类型和规范类型 #
让我们以一些看起来很眼熟的东西作为开始。 规范使用了一些值例如 undefined
, true
, 和 false
, 我们早已从 JavaScript 中了解它们了。它们都是 语言值, 即规范定义的 语言类型 的值。
规范内部也使用了语言值,举个例子,一个内部的数据类型可能包含一个值是 true
和 false
的字段。与此相反,JavaScript 引擎内部通常不会使用语言值。例如:如果 JavaScript 引擎是用 C++写的,它通常会使用 C++的 true
和 false
(而不是其 JavaScript 的 true
和 false
的内部表示形式)。
除了语言类型之外,规范还使用了 规范类型,规范类型是只存在在规范中的类型,而不在 JavaScript 语言中。JavaScript 引擎不需要(但完全可以)去实现它们。在这篇文章中,我们将会去了解规范类型记录(和它的子类型完成记录)。
抽象操作 #
抽象操作 是定义在 ECMAScript 规范中的函数;定义它们的目的是为了更简洁的书写规范。JavaScript 引擎内部不需要将它们作为单独的函数来实现。这些函数不能直接被 JavaScript 调用。
内部槽位和内部方法 #
内部槽位 和 内部方法 使用包含在 [[ ]]
中的名称。
内部槽位是 JavaScript 对象或者规范类型的数据成员。它们被用来存储对象的状态。内部方法是 JavaScript 对象的成员方法。
例如,每个 JavaScript 都有一个内部槽位 [[Prototype]]
和内部方法 [[GetOwnProperty]]
。
内部槽位和方法是无法从 JavaScript 进行访问的。比如,你不能访问 o.[[Prototype]]
或者调用 o.[[GetOwnProperty]]()
。为了自己内部的的使用,JavaScript 引擎可以实现此功能,但是没有必要。
有时候内部方法委托给相似名称的抽象操作,例如普通对象的 [[GetOwnProperty]]:
当
O
的内部方法[[GetOwnProperty]]
被属性键P
调用时, 将会执行以下步骤:
- 返回
! OrdinaryGetOwnProperty(O, P)
。
(我们将在下一章中了解感叹号的含义。)
OrdinaryGetOwnProperty
不是内部方法,因为它不与任何对象相关联;相反的,它将所操作的对象作为一个参数传递。
OrdinaryGetOwnProperty
被叫做 “ordinary”(普通) 是因为它只操作普通对象。ECMAScript 对象可以是普通对象 或者 异质对象。普通对象必须对一组被称为 基本内部方法 的方法具有默认行为。如果一个对象偏离了默认行为,那它就是异质对象。
最著名的异质对象就是 Array
,因为它的 length 属性表现出了非默认的行为:设置 length
属性可能会从 Array
删除元素。
基本内部方法在此处列举 。
完成记录 #
问号和感叹号又是什么意思呢?为了理解它们,我们需要去浏览 完成记录!
完成记录是一种规范类型(仅为了规范目的而定义)。JavaScript 引擎不需要有一个相关的内部数据类型。
完成记录是一种记录 —— 有一组固定的命名字段的数据类型。一个完成记录包含三个字段:
名字
描述
[[Type]]
normal
, break
, continue
, return
, 或 throw
其中之一。除了 normal
之外的其它类型被称为 硬性完成。
[[Value]]
完成时生成的值,例如函数的返回值或者异常(如果有异常抛出)。
[[Target]]
用于定向控制转移(与本文无关)。
每个抽象操作会隐式返回一个完成记录。即使抽象操作看起来像是会返回一个基本数据类型比如布尔值,这个值也是会被包裹在一个 normal
类型的完成记录里面(见 隐式完成值)。
注 1 :规范在这一方面也不是完全一致的;有一些辅助函数会返回裸值,并且其返回值按原样使用,而不需要从完成记录里提取值。这在上下文中通常是很清楚的。
注 2 :规范编辑正在考虑更加显式地处理完成记录。
如果一个算法抛出异常,那意味着返回了一个 [[Type]]
为 throw
, [[Value]]
为异常对象的完成记录。在这里,我们先忽略 break
, continue
和 return
。
ReturnIfAbrupt(argument)
表示执行以下步骤:
- 若
argument
为硬性完成,返回argument
;- 设置
argument
为argument.[[Value]]
。
也就是说,我们会检查一个完成记录,如果是硬性完成,则立即返回。否则,我们将会从完成记录中取值。
ReturnIfAbrupt
可能看起来像是一个函数调用,但其实不然。它导致了 ReturnIfAbrupt()
生成位置的函数的返回,而不是 ReturnIfAbrupt()
函数本身。这种行为更像是 C 语言中的宏。
ReturnIfAbrupt
可以像这样使用:
- 令
obj
为Foo()
;(obj
是一个完成记录。)ReturnIfAbrupt(obj)
。Bar(obj)
。 (如果我们到了这一步,obj
已经是一个从完成记录里面提取的值了。)
现在该说到问号了: ? Foo()
等价于 ReturnIfAbrupt(Foo())
。 使用简写是实用的: 我们不需要每一次都显式地编写错误处理代码。
类似地, 令 val 为 ! Foo()
等价于:
- 令
val
为Foo()
;- 断言:
val
不是一个硬性完成;- 设
val
为val.[[Value]]
;
知道了这些之后,我们可以像这样重写 Object.prototype.hasOwnProperty
:
Object.prototype.hasOwnProperty(V)
- 令
P
为ToPropertyKey(V)
;- 若
P
是一个硬性完成,返回P
。- 设
P
为P.[[Value]]
;- 令
O
为ToObject(this value)
;- 若
O
是一个硬性完成。返回O
。- 设
O
为O.[[Value]]
;- 令
temp
为HasOwnProperty(O, P)
;- 若
temp
是硬性完成,返回temp
;- 令
temp
为temp.[[Value]]
;- 返回
NormalCompletion(temp)
。
我们也可以像这样重写 HasOwnProperty
:
HasOwnProperty(O, P)
- 断言:
Type(O)
为Object
;- 断言:
IsPropertyKey(P)
为true
;- 令
desc
为O.[[GetOwnProperty]](P)
;- 若
desc
不是一个硬性完成,返回desc
。- 设
desc
为desc.[[Value]]
;- 若
desc
为undefined
, 返回NormalCompletion(false)
;- 返回
NormalCompletion(true)
。
我们也可以重写 [[GetOwnProperty]]
内部方法且不带感叹号:
O.[[GetOwnProperty]]
- 令
temp
为OrdinaryGetOwnProperty(O, P)
;- 断言:
temp
是一个硬性完成;- 令
temp
为temp.[[Value]]
;- 返回
NormalCompletion(temp)
。
这里我们假设 temp
是一个新的临时变量,它不与其它任何变量产生冲突。
我们还使用了这个知识:当返回语句返回的不是完成记录时,返回值会被隐式地包裹在一个 NormalCompletion
中。
拓展知识: Return ? Foo()
#
规范使用了 Return ? Foo()
,为什么这里要写问号呢?
Return ? Foo()
展开写是:
- 令
temp
为Foo()
;- 若
temp
是硬性完成,返回temp
;- 设
temp
为temp.[[Value]]
;- 返回
NormalCompletion(temp)
。
这和 Return Foo()
是一样的;它在硬性完成和正常完成中表现出一样的行为。
Return ? Foo()
仅仅只用于编辑,来使得 Foo
更显式地返回一个完成记录。
断言 #
断言在规范中断言了算法的不变条件。它们是为了明确起见而添加的, 但是并不要求实现它们,也就是说,实现并不要求检查这些条件。
继续 #
抽象操作委托给其它抽象操作(见下图),但是基于这篇文章我们应该能够弄清楚它们做了什么。我们会遇到属性操作符,这是另一种规范类型。
函数调用图从 Object.prototype.hasOwnProperty
开始
小结 #
我们通读了一个简单的方法 Object.prototype.hasOwnProperty
,和它所调用的抽象操作 。熟悉了与错误处理有关的简写 ?
和 !
。我们也了解了 语言类型, 规范类型, 内部槽位, 和 内部方法。
相关链接 #
如何阅读 ECMAScript 规范: 此教程从一个稍微不同的角度涵盖了这篇文章中的大部分素材。
转载自:https://juejin.cn/post/7199440953400016951