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

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

1周前
161012

导语

 
即使您了解 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 规范 打下了很好的基础。
分类:
标签:
请先登录,再评论

👍

1周前

为你推荐

一次 Node.js http 连接无法复用的问题排查
一次压测中阿里云 SLB 的并发连接数被打满了,导致服务之间的 HTTP 调用延迟很大。当时 SLB 的并发连接数情况如下图所示。登录容器终端查看,发现某个前端 Node.js 服务中的单个容器的 E
Chrome V8 javascript engine C++ 高性能垃圾收集器 Oilpan
这篇文章是 Oilpan 系列文章,它将概述 Oilpan 的核心原理及其 C++ API。 在这篇文章中,我们将介绍一些支持的特性,解释它们如何与垃圾收集器的各种子系统交互,并深入探讨在清扫器中并发回收对象。
深入了解 Chrome V8 javascript engine 的垃圾收集引擎
V8 使用分代垃圾收集器,将 Javascript 堆拆分为用于新分配对象的小型年轻代和用于长期存活对象的大型老年代。 由于大多数对象在年轻时死亡,这种分代策略使垃圾收集器能够在较小的年轻代(称为清除)中执行常规的、短时间的垃圾收集,而无需跟踪年老代中的对象。
Chrome V8 javascript engine 的3种垃圾回收算法
从 v6.2 开始,V8 将用于收集年轻代的默认算法切换为并行 Scavenger,本文介绍V8的3种垃圾回收算法:V8 年轻代切尼半空间复制垃圾回收算法、V8 年轻代并行 Mark-Evacuate 垃圾回收算法、V8 年轻代并行 Scavenger 垃圾回收算法
跟着V8引擎读懂 ECMAScript 规范(一)
在本文中,我们通过阅读分析一个简单的方法 —— Object.prototype.hasOwnProperty —— 以及它调用的抽象操作,熟悉了速记 ?和 !与错误处理有关,了解了语言类型、规范类型、内部属性和内部方法。对大家更好地阅读理解 ECMAScript 规范 打下很好的基础。
跟着V8引擎读懂 ECMAScript 规范(二)
本文我们研究了ECMAScript规范如何定义语言特征,在本例中了解到了原型链遍历,跨越所有不同的层:触发特征的句法结构和定义它的算法。
跟着V8引擎读懂 ECMAScript 规范(三)
在本文,我们熟悉了词法语法、句法语法以及用于定义句法语法的简写。 我们研究了在异步函数中禁止使用 await 作为标识符,但在非异步函数中它却可以使用的相关定义。
跟着V8引擎读懂 ECMAScript 规范(四)
在本文中,我们研究了 ECMAScript 规范如何定义覆盖语法(cover grammar),并在我们无法基于有限前瞻(finite lookahead)识别当前句法结构的情况下使用它们。我们研究了将箭头函数参数列表与带括号的表达式区分开来,以及规范如何使用覆盖语法先允许解析看起来模棱两可的结构,