跟着V8引擎读懂 ECMAScript 规范(一)原创
2年前
374934
导语
即使您了解 JavaScript,阅读其语言规范、ECMAScript 语言规范或简称 ECMAScript 规范也可能会令人生畏。 至少当我第一次开始阅读它时,我是这样认为的。
让我们从一个具体的例子开始,并通过标准来理解它。 以下代码演示了 Object.prototype.hasOwnProperty 的用法:
const o = { foo: 1 };
o.hasOwnProperty('foo'); // true
o.hasOwnProperty('bar'); // false
在此示例中,o 没有名为 hasOwnProperty 的属性,因此我们沿着原型链查找它。 我们在 o 的原型中找到它,即 Object.prototype。
为了介绍 Object.prototype.hasOwnProperty 的工作原理,规范中使用了类似如下伪代码进行描述:
When the hasOwnProperty method is called with argument V, the following steps are taken:1. Let P be ? ToPropertyKey(V).2. Let O be ? ToObject(this value).3. Return ? HasOwnProperty(O, P).
以及
The abstract operation HasOwnProperty is used to determine whether an object has an own property with the specified property key. A Boolean value is returned. The operation is called with arguments O and P where O is the object and P is the property key. This abstract operation performs the following steps:1. Assert: Type(O) is Object.2. Assert: IsPropertyKey(P) is true.3. Let desc be ? O.[[GetOwnProperty]](P).4. If desc is undefined, return false.5. Return true.
但什么是“abstract operation”?[[]]里边的内容表示什么?为什么在函数前会有个问号?这些 Assert 又是什么意思?
语言类型和规范类型
让我们从看起来很熟悉的东西开始。 该规范使用诸如 undefined、true 和 false 之类的值,我们已经从 JavaScript 中知道了这些值。 它们都是语言值,规范中对语言类型的值也有定义。
此规范还在内部使用语言值,例如,内部数据类型可能包含可能值为 true 和 false 的字段。 相比之下,JavaScript 引擎通常不会在内部使用语言值。 例如,如果 JavaScript 引擎是用 C++ 编写的,它通常会使用 C++ 的 true 和 false(而不是 JavaScript 的 true 和 false 的内部表示)。
除了语言类型,规范中还会使用规范类型,这些类型只出现在规范中,而不出现在 JavaScript 语言中。 JavaScript 引擎不需要(想也可以)实现它们。 在本文中,我们将了解规范类型 Record(及其子类型 Completion Record)。
抽象操作
抽象操作(abstract operation)是 ECMAScript 规范中定义的函数; 它们的定义是为了简洁地编写规范。 JavaScript 引擎不必在引擎内部将它们实现为单独的函数。 它们不能直接从 JavaScript 调用。
内部属性和内部方法
内部属性和内部方法使用包含在 [[ ]] 中的名称。
内部属性是 JavaScript 对象或规范类型的数据成员。 它们用于存储对象的状态。 内部方法是 JavaScript 对象的成员函数。
例如,每个 JavaScript 对象都有一个内部属性 [[Prototype]] 和一个内部方法 [[GetOwnProperty]]。
无法从 JavaScript 访问内部属性和内部方法。 例如,不能访问 o.[[Prototype]] 或调用 o.[[GetOwnProperty]]()。 JavaScript 引擎可以实现它们以供自己内部使用,但不是必须的。
有时内部方法会委托给相似命名的抽象操作,例如在普通对象的 [[GetOwnProperty]] 的情况下:
When the [[GetOwnProperty]] internal method of O is called with property key P, the following steps are taken:1. Return ! OrdinaryGetOwnProperty(O, P).
(我们将在下一章中了解感叹号的含义。)
OrdinaryGetOwnProperty 不是内部方法,因为它不与任何对象关联; 相反,它操作的对象是通过参数传递进去的。
OrdinaryGetOwnProperty 之所以称为“普通”,是因为它对普通对象进行操作。 ECMAScript 对象可以是普通的也可以是奇异的。 普通对象必须具有一组方法的默认行为,称为基本内部方法。 如果一个对象偏离了默认行为,它就是奇异的。
最著名的奇异对象是 Array,因为它的 length 属性以非默认方式运行:如,设置 length 属性可以从 Array 中删除元素。
规范类型Completion Record
问号和感叹号呢? 要了解它们,我们需要查看Completion records!
Completion Record是一种规范类型(仅为规范目的而定义)。 JavaScript 引擎不必具有相应的内部数据类型。
Completion Record是一条“记录”——一种具有一组固定命名字段的数据类型。 完成记录具有三个字段:
命名 | 描述 |
---|---|
[[Type]] |
normal , break , continue , return , throw 其中之一,除 normal 以外都属 abrupt completions |
[[Value]] |
完成发生时产生的值,例如,函数返回值或抛异常(如果抛出异常) |
[[Target]] |
用于定向控制传输 |
每个抽象操作都隐式返回一个完成记录。 即使它看起来像一个抽象操作会返回一个简单的类型,例如布尔值,它也被隐式地包装到一个具有普通类型的完成记录中。
注1:规范在这方面并不完全一致; 有一些辅助函数返回裸值并且其返回值按原样使用,而不从完成记录中提取值。 这通常从上下文中很清楚。
注 2:此规范的编辑们正在研究使完成记录处理更加明确。
如果算法抛出异常,则意味着返回带有 [[Type]] throw 的 Completion Record,其 [[Value]] 是异常对象。 我们暂时忽略 break、continue 和 return 类型。
ReturnIfAbrupt(argument) 的执行步骤:
1. If argument is abrupt, return argument2. Set argument to argument.[[Value]].
也就是说,我们检查完成记录; 如果是突然完成,我们立即返回。 否则,我们从完成记录中提取值。
ReturnIfAbrupt 可能看起来像一个函数调用(但它不是)。 它导致发生 ReturnIfAbrupt() 的函数返回,而不是 ReturnIfAbrupt 函数本身。 它的行为更像是类 C 语言中的宏。
ReturnIfAbrupt 可以这样使用:
1. Let obj be Foo(). (obj is a Completion Record.)2. ReturnIfAbrupt(obj).3. Bar(obj). (If we’re still here, obj is the value extracted from the Completion Record.)
现在问号开始起作用了:? Foo() 等价于 ReturnIfAbrupt(Foo())。 使用简写是实用的:我们不需要每次都显式地编写错误处理代码。
同样,令 val 为 ! Foo() 等价于:
1. Let val be Foo().2. Assert: val is not an abrupt completion.3. Set val to val.[[Value]].
利用这些知识,我们可以像这样重写 Object.prototype.hasOwnProperty:
Object.prototype.hasOwnProperty(V)1. Let P be ToPropertyKey(V).2. If P is an abrupt completion, return P3. Set P to P.[[Value]]4. Let O be ToObject(this value).5. If O is an abrupt completion, return O6. Set O to O.[[Value]]7. Let temp be HasOwnProperty(O, P).8. If temp is an abrupt completion, return temp9. Let temp be temp.[[Value]]10. Return NormalCompletion(temp)
我们可以像这样重写 HasOwnProperty:
HasOwnProperty(O, P)1. Assert: Type(O) is Object.2. Assert: IsPropertyKey(P) is true.3. Let desc be O.[[GetOwnProperty]](P).4. If desc is an abrupt completion, return desc5. Set desc to desc.[[Value]]6. If desc is undefined, return NormalCompletion(false).7. Return NormalCompletion(true).
也可以重写不带感叹号的 [[GetOwnProperty]] 内部方法:
O.[[GetOwnProperty]]1. Let temp be OrdinaryGetOwnProperty(O, P).2. Assert: temp is not an abrupt completion.3. Let temp be temp.[[Value]].4. Return NormalCompletion(temp).
这里我们假设 temp 是一个全新的临时变量,不会与其他任何东西发生冲突。
我们还使用了这样的知识,即当 return 语句返回的不是完成记录时,它会隐式包装在 NormalCompletion 中。
Return ? Foo()
该规范使用符号 Return ? Foo() - 为什么是问号?
Return ? Foo() 阐述如下:
1. Let temp be Foo().2. If temp is an abrupt completion, return temp.3. Set temp to temp.[[Value]].4. Return NormalCompletion(temp).
与 Return Foo(); 相同,对于突然完成和正常完成,它的行为方式相同。
返回 ? Foo() 仅用于编辑原因,以使 Foo 返回完成记录更加明确。
断言
断言(asserts),在规范中断言算法的不变条件。 为了清楚起见,添加了它们,但不向实现添加任何要求——实现不需要检查它们。
抽象操作委托给其他抽象操作(见下图),我们应该能够弄清楚它们的作用。 我们会遇到属性描述符,它只是另一种规范类型。
从 Object.prototype.hasOwnProperty 开始的函数调用图
小结
在本文中,我们通过阅读分析一个简单的方法 —— Object.prototype.hasOwnProperty —— 以及它调用的抽象操作,熟悉了速记 ?和 !与错误处理有关,了解了语言类型、规范类型、内部属性和内部方法。对大家更好地阅读理解 ECMAScript 规范 打下了很好的基础。
点赞收藏
分类: