做者:Marja Hölttä翻译:疯狂的技术宅前端
原文:https://v8.dev/blog/understan...程序员
未经容许严禁转载面试
即使你对 JavaScript 很了解,可是去阅读 ECMAScript 语言规范,或简称为 ECMAScript 规范也会使人生畏。至少这是我在第一次开始阅读时的感觉。算法
让咱们从一个具体的例子开始,而后经过遍历整个规范以了解它。如下代码演示了 Object.prototype.hasOwnProperty
的用法:segmentfault
const o = { foo: 1 }; o.hasOwnProperty('foo'); // true o.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)
。
和ecmascript
抽象操做
HasOwnProperty
用于肯定对象是否具备带有指定属性键的本身的属性。其返回一个布尔值。该操做使用参数O
和P
调用,其中O
是对象,而P
是属性键。此抽象操做执行如下步骤:
- 断言:
Type(O)
是Object
。- 断言:
IsPropertyKey(P)
为true
。- 使
desc
为? O.[[GetOwnProperty]](P)
。- 若是
desc
为undefined
,则返回false
。- 返回
true
。
但什么是“抽象操做”呢? [[]]
里面有什么东西?为何在函数前面有一个?
?这些断言又是什么意思?
快来找出答案吧!
让咱们从看上去熟悉的东西开始。规范使用了咱们从 JavaScript 中已经知道的值,例如 undefined
,true
和 false
。它们都是 语言值,即规范中所定义的 语言类型的值。
规范还在内部使用语言值,例如,内部数据类型可能包含一个字段,其可能值为 true
和 false
。相反,JavaScript 引擎一般在内部不使用语言值。例如,若是 JavaScript 引擎是用 C ++ 编写的,则一般会使用 C++ 的true
和 false
(而不是 JavaScript 的 true
和 false
的内部表示)。
除语言类型外,规范还使用规格类型,这些类型仅在规范中出现,但不属于 JavaScript 语言。 JavaScript 引擎不须要(但能够)实现它们。在本文中,咱们将了解规范类型 Record(及其子类型 Completion Record)。
抽象操做是 ECMAScript 规范中定义的函数;定义它们是为了简洁地编写规范。 JavaScript 引擎没必要将其做为单独的函数实如今引擎内部。不能从 JavaScript 直接调用它们。
内部插槽和内部方法使用包含在 [[]]
中的名称。
内部插槽是 JavaScript 对象或规范类型的数据成员。它们被用于存储对象的状态。内部方法是 JavaScript 对象的成员函数。
例如,每一个 JavaScript 对象都有一个内部插槽 [[Prototype]]
和一个内部方法 [[GetOwnProperty]]
。
没法从 JavaScript 访问内部插槽和方法。例如,你没法访问 o.[[Prototype]]
或调用 o.[[GetOwnProperty]]()
。 JavaScript 引擎能够实现它们以供内部使用,但并非必须的。
有时内部方法委托相似名称的抽象操做,例如在普通对象的 [[GetOwnProperty]]
中:
[[GetOwnProperty\]](P)
当使用属性键
P
调用O
的[[GetOwnProperty]]
内部方法时,将执行如下步骤:返回
! OrdinaryGetOwnProperty(O,P)
。
(咱们将在下一章中找到感叹号的含义。)
OrdinaryGetOwnProperty
不是内部方法,由于它没有与任何对象相关联;而是将对其进行操做的对象做为参数传递。
由于 OrdinaryGetOwnProperty
对普通对象起做用,因此被称为“普通”。 ECMAScript 对象能够是普通或外部的。普通对象必须具备一组被称为基本内部方法的方法的默认行为。若是某个对象偏离默认行为,则该对象是外部的。
最著名的外部对象是 Array,由于其 length
属性以非默认方式运行:设置 length
属性能够从 Array
中删除元素。
基本的内部方法是 https://tc39.es/ecma262/#table-5 中列出的方法。
问号和感叹号是怎么回事?要了解它们,咱们须要研究完成记录(Completion Records)!
完成记录是一种规范类型(仅出于规范目的而定义)。 JavaScript 引擎没必要具备相应的内部数据类型。
完成记录是一种“记录”——一种具备一组固定的命名字段的数据类型。完成记录包含三个字段:
名称 | 描述 |
---|---|
[[Type]] |
normal , break , continue , return 或 throw 中的一个。 除 normal 之外的全部其余类型都是忽然完成”( abrupt completions) |
[[Value]] |
完成发生时产生的值,例如:函数的返回值或异常(若是引起了异常的话)。 |
[[Target]] |
用于定向控制转移(与本文无关)。 |
每一个抽象操做都隐式返回完成记录。即便看起来抽象操做会返回一个简单的类型(例如 Boolean),它也将被隐式包装为类型为 normal
的完成记录(请参见隐式完成值。
注1:规格在这方面并不彻底一致;有些辅助函数返回裸值,而且其返回值按原样使用,而无需从“完成记录”中提取值。一般从上下文中能够清楚地看出这一点。
注2:规范制定人员正在研究使“完成记录”的处理更加明确。
若是算法抛出异常,则意味着返回带有 [[Type]]
throw
的完成记录,而 [[Value]]
是异常对象。如今,咱们将忽略 break
, continue
和 return
类型。
ReturnIfAbrupt(argument)
意味着采起如下步骤:
- 若是
argument
忽然出现,则返回argument
- 将
argument
设置为argument.[[Value]]
也就是说,咱们检查完成记录;若是是忽然完成,会当即返回。不然从完成记录中提取值。
ReturnIfAbrupt
可能看起来像一个函数调用,但事实并不是如此。它是致使返回 ReturnIfAbrupt()
的函数返回的缘由,而不是返回 ReturnIfAbrupt
函数自己的函数。它的行为更像是 C 语言中的宏。
ReturnIfAbrupt
能够这样使用:
- 令
obj
为Foo()
。 (obj
是 Completion Record。)ReturnIfAbrupt(obj)
Bar(obj)
. (若是仍在此处,则obj
是从 Completion Record 中提取的值)
如今问号开始起做用:? Foo()
等同于 ReturnIfAbrupt(Foo())
。使用简写很实用:咱们没必要每次都明确编写错误处理代码。
一样, Let val be ! Foo()
等效于:
- 使
val
为Foo()
- 断言:
val
不是忽然完成的- 将
val
设置为val.[[Value]]
利用这些知识,咱们能够这样重写 Object.prototype.hasOwnProperty
:
Object.prototype.hasOwnProperty(P)
- 使
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
是一个全新的临时变量,不会与其余任何冲突。
咱们还使用了如下知识:当 return 语句返回除 Completion Record 之外的其余内容时,它隐式包装在 NormalCompletion
中。
Return ? Foo()
规范使用 Return ? Foo()
——为何有问号?
Return ? Foo()
扩展为:
- 使
temp
为Foo()
- 若是
temp
是忽然完成的,则返回temp
- 将
temp
设置为temp.[[Value]]
- 返回
NormalCompletion(temp)
与 Return Foo()
相同;对于忽然和正常完成,其行为方式相同。
Return ? Foo()
仅出于编辑缘由而使用,以使其可以更明确地表达 Foo 返回完成记录。
规范中的主张断言了算法的不变条件。为了清楚起见,添加了它们,但没有对实现添加任何要求——实现中不须要检查它们。
咱们已经创建了阅读规范所需的知识,如 Object.prototype.hasOwnProperty
之类的简单方法和诸如 HasOwnProperty
之类的抽象操做。它们仍然委托其余抽象操做,可是基于本文,咱们应该可以弄清楚它们的做用。咱们将遇到属性描述符,这是另外一种规范类型。
如何阅读 ECMAScript 规范(https://timothygu.me/es-howto/):该教程从一个稍微不一样的角度涵盖了本文中的许多内容。