本片文章中,我们采用标准中的一个简单的函数并试图理解它的注解。Let’s go!
前言
即使你熟悉JavaScript,但是当你阅读这门编程语言的标准时(EMCAScript Language specfication)你可能也会感觉这是一件非常难的事情。至少对于我来说,我第一次阅读标准时的感觉就是这样的。
现在,让我们用一个具体的例子来走查ECMA标准并试图理解它。下面的代码展示了Object.prototype.hasOwnProperty
的用法:
const o = { foo: 1 }; o.hasOwnProperty('foo'); // true o.hasOwnProperty('bar'); // false
在这个例子中,o并没有一个叫做hasOwnProperty
的属性,所以我们沿着它的原型链向上查找属性。我们在o的原型(Object.prototype
)中找到了这个hasOwnProperty
属性,
为了表述Object.prototype.hasOwnProperty
这个方法是如何工作的,ECMA标准使用了下面的为代码来说明:
Object.prototype.hasOwnProperty(V)
When the hasOwnProperty method is called with argument V, the following steps are taken:
- Let P be ? ToPropertyKey(V).\
- Let O be ? ToObject(this value).\
- Return ? HasOwnProperty(O, P).
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:
- Assert: Type(O) is Object.
- Assert: IsPropertyKey(P) is true.
- Let desc be ? O.[[GetOwnProperty]](P).
- If desc is undefined, return false.
- Return true.
等等。这里有几个问题:
- 什么是”abstract operation”?
- 两个方括号[[ ]]之间的东西是什么鬼?
- 为什么在函数的前面有一个问好 “?”
- 这里的断言又是什么意思
让我们来搞清楚这些问题!
语言类型与标准类型 (Language types and specification types)
让我们从一些看起来很熟悉的东西开始吧。在标准中使用一些诸如:undefined, true, false
这类的值,我们已经在JavaScript中对它们很熟悉了。它们是language values
(标准中所定义的language types
对应的值)
标准同样在内部使用了language values
,例如:一个内部数据类型可能包含了一个值为true或false的域(field)。与之相反,JavaScript引擎通常不会在内部使用language values
.例如:如果一个JavaScript的引擎使用C++来编写,那么它通常会使用C++语言中的true和false(不是JavaScript自身的true和false)
笔者注:笔者理解这里原作者的意思可能是想表达标准中的一些数据类型可能会引用它定义的其他数据类型。而js引擎的数据类型取决于实现引擎本身的语言的数据类型。
除了language types
之外,标准中也使用了specification types
,它仅仅只出现在标准中,不会出现在JavaScript语言中,也就是说JavaScript引擎不需要去实现这些类型。本文中,我们会去了解到specification type
中的Record
类型(它的子类型Completion Record
)
抽象操作(Abstract operations)
Abstract operations
是被定义在ECMAScript标准中的一些函数。它们被设计出来的意图自在于能够很简明的去编写标准。JavaScript引擎不会将它们作为单独的函数去实现。这些函数也不能在JavaScript中被调用。
内部插槽和内部方法(Internal slots and internal methods)
Internal slots and internal methods
就是被两个方括号包围的东西[[]]
Internal slots
是JavaScript对象或specification type
的一些数据成员。它们被用于存储对象的状态。
Internal methods
是JavaScript对象的一些成员方法。
例如,每一个JavaScript对象都有一个internal slot
[[Prototype]]和一个 internal method
[[GetOwnProperty]].
internal slots
和internal methods
不能被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:
- Return ! OrdinaryGetOwnProperty(O, P).
(我们会在下一章找到这个感叹号!表示的意义)
OrdinaryGetOwnProperty
它不是一个内部方法,因为它没有和任何的对象所关联,相反,它所操作的对象是被作为参数所传递的。
OrdinaryGetOwnProperty
被称为”ordinary”是因为它在一个普通的对象上进行操作。ECMAScript对象可以是ordinary或者是exotic。Ordinary对象对于一系列被称为essential internal methods
的方法一定是有默认的行为的。如果一个对象背离了这些默认行为,那么这个对象是exotic的。
最为我们熟知的呃对象就是Array
,因为他的length属性行为是非默认的:设置length属性会从Array中移除一些元素。
Essential internal methods列表点此查看
Completion Records
之前我们看到的 “?” 和 “!” 这两个符号到底是什么?为了弄清楚这个问题,我们需要把目光转向Completion Records
!
Completion Records
是一个specification type
(这里再次说明,specification type仅仅是为了表述标准而定义的类型)。JavaScript引擎中没有与之对应的internal data type
Completion Record
是一个Record
类型,这是一种拥有固定的域的数据类型。一个 Completion Record
有三个域:
Name | Description |
---|---|
[[Type]] | One of: normal, break, continue, return, or throw. All other types except normal are abrupt completions. |
[[Value]] | The value that was produced when the completion occurred, for example, the return value of a function or the exception (if one is thrown). |
[[Target]] | Used for directed control transfers (not relevant for this blog post). |
每一个抽象操作隐式的返回了一个Completion Record
. 即使是一个看起来像是返回了一个简单类型(Boolean)的抽象操作,它依然被隐式地的包装成了normal类型的Completion Record
(参考Implicit Completion Values)
如果一个算法抛出了一个异常,这意味着返回的拥有[[Type]]的Completion Record
抛出的[[Value]]是一个异常对象。我们会忽视 break, continue
和return
类型。
ReturnIfAbrupt(argument)
意思是按下列步骤:
- 如果argument是abrupt的,返回argument
- 设置argument为argument.[[Value]]
上面这个抽象操作就是说,我们检查一个Completion Record
,如果它是一个abrupt completion
,我们就直接返回它。否则,我们从这个 Completion Record
中提取其值。
ReturnIfAbrupt
看起来像是一个函数调用,但是并不是。它导致ReturnIfAbrupt
的函数返回。它更像是C语言中的宏。
ReturnIfAbrupt
可能被这样使用:
- Let obj be Foo(). (obj is a Completion Record.)
- ReturnIfAbrupt(obj).
- Bar(obj). (If we’re still here, obj is the value extracted from the Completion Record.)
现在是时候揭晓 “?” 的意义了:?Foo()
等价于 ReturnIfAbrupt(Foo())
使用这样的缩写是很有讲究的:我们不需要每次都显式的编写错误处理代码。
相似的,val be !Foo()
等价于:
Let val be Foo().
Assert: val is not an abrupt completion.
Set val to val.[[Value]].
使用这些知识,我们可以重写 Object.prototype.hasOwnProperty
,就像这样:
- Let P be ToPropertyKey(V).
- If P is an abrupt completion, return P
- Set P to P.[[Value]]
- Let O be ToObject(this value).
- If O is an abrupt completion, return O
- Set O to O.[[Value]]
- Let temp be HasOwnProperty(O, P).
- If temp is an abrupt completion, return temp
- Let temp be temp.[[Value]]
- Return NormalCompletion(temp)
然后,我们可以重写HasOwnProperty
,如下:
- Assert: Type(O) is Object.
- Assert: IsPropertyKey(P) is true.
- Let desc be O.[GetOwnProperty].
- If desc is an abrupt completion, return desc
- Set desc to desc.[[Value]]
- If desc is undefined, return > NormalCompletion(false).
- Return NormalCompletion(true).
我们依然可以不使用感叹号!重写[GetOwnProperty]
内部方法
- Let temp be OrdinaryGetOwnProperty(O, P).
- Assert: temp is not an abrupt completion.
- Let temp be temp.[[Value]].
- Return NormalCompletion(temp).
我们这里假设了temp
是一个全新的与其他任何没有冲突的临时变量。
这里我们也用到了当一个返回语句返回了除开Completion Record
的类型时,我们会将其隐式地包装成NormalCompletion
类型的知识。
Side track: Return ? Foo()
标准中使用 Return ? Foo()
——为什么要使用 “?”
Return ? Foo()
可以被展开为以下的语句:
- Let temp be Foo().
- If temp is an abrupt completion, return temp.
- Set temp to temp.[[Value]].
- Return NormalCompletion(temp).
它与 Return Foo()
其实是一样的,”?”对与abrupt和normal类型的Completion
,可以使它们保持相同的行为。
Return ? Foo()
仅仅只是因为编辑的原因,这样可以使Foo返回一个Completion Record
的意思更加的清晰。
Asserts
标准中,Asserts断言一个算法不变的条件。它们被添加是为了清晰,但是不会像实现中添加任何的依赖,实现不需要去检查它们。
更多
抽象操作委托给其他抽象操作(见下图),基于这篇文章,我们应该能够弄清楚它们是做什么的。我们会遇到属性描述符,它只是另一种规范类型。
总结
我们阅读完了一个简单的方法Object.prototype.hasOwnProperty
的标准和它相关的一些抽象操作。我们是否熟悉了这些处理错误的缩写: “?” 和 “!”?我们也遇到了 language types
, specification types
, internal slots
, 和internal methods
。
笔者总结
language type
: 语言类型,就是指语言本身内置的类型,例如js中的number, string, boolean,其对应的language value
为:1, ‘a’, true…
specification type
: 标准类型,是语言标准中为了辅助描述标准的内容制定的一些类型,语言的引擎不会去实现这些类型。
internal slots
: 就是用[[]]表示一些属性,我们不能通过JavaScript去访问这些属性。JavaScript引擎可以选择去实现它们以供内部使用,但这不是必须的。
internal methods
: 类似于internal slots
,是用[[]]表示一些方法,我们同样不能用JavaScript去调用这些方法。同样,JavaScript引擎可以选择实现这些方法,但也不是必须。
! 和 ?
: 缩写的表示法,一般用于处理错误代码,与C语言中的“宏”类型,具体的表示参照上文。