性能文章>跟着V8引擎读懂 ECMAScript 规范(一)>

跟着V8引擎读懂 ECMAScript 规范(一)原创

1年前
270634

导语

 
即使您了解 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, returnthrow 其中之一,除 normal 以外都属 abrupt completions
[[Value]]

完成发生时产生的值,例如,函数返回值或抛异常(如果抛出异常)

[[Target]]

用于定向控制传输

 
每个抽象操作都隐式返回一个完成记录。 即使它看起来像一个抽象操作会返回一个简单的类型,例如布尔值,它也被隐式地包装到一个具有普通类型的完成记录中。
 
注1:规范在这方面并不完全一致; 有一些辅助函数返回裸值并且其返回值按原样使用,而不从完成记录中提取值。 这通常从上下文中很清楚。
 
 
注 2:此规范的编辑们正在研究使完成记录处理更加明确。
 
 
如果算法抛出异常,则意味着返回带有 [[Type]] throw 的 Completion Record,其 [[Value]] 是异常对象。 我们暂时忽略 break、continue 和 return 类型。
 
 
ReturnIfAbrupt(argument) 的执行步骤:
 
1. If argument is abrupt, return argument
2. 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 P
3. Set P to P.[[Value]]
4. Let O be ToObject(this value).
5. If O is an abrupt completion, return O
6. Set O to O.[[Value]]
7. Let temp be HasOwnProperty(O, P).
8. If temp is an abrupt completion, return temp
9. 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 desc
5. 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 开始的函数调用图
 
 

小结

 
在本文中,我们通过阅读分析一个简单的方法 —— Object.prototype.hasOwnProperty —— 以及它调用的抽象操作,熟悉了速记 ?和 !与错误处理有关,了解了语言类型、规范类型、内部属性和内部方法。对大家更好地阅读理解 ECMAScript 规范 打下了很好的基础。
点赞收藏
分类:标签:
风之石
请先登录,查看3条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步
4
3