前言
前段時間身邊有一個小伙伴問了一個關于 JavaScript 基礎的問題,題目如下:
const o = {
m() { console.log(this === o) }
}
(o.m)()
當時我的理解是,根據語法分析首先執行 o.m
的取值操作,然后其結果進行調用。因為 o.m
的執行結果是一個函數,然后函數直接被調用,所以其 this
應該在嚴格模式下是 undefined
,非嚴格模式下是global
,結果為 false
。
但是實際執行結果為 true
,也就是這種調用方式和 o.m()
行為表現是一致的。再簡單搜尋無果之后,我轉向了 ECMAScript 規范,并在其中找到了答案。
屬性訪問
o.m
這種操作在規范中稱為 Property Accessors 其返回的結果并不是對象 o
的 m
屬性所對應的值,而是返回一個 Reference 類型的值。
- Return a value of type Reference whose base value component is bv, whose referenced name component is propertyKey, and whose strict reference flag is strict.
Reference
類型的值對應著一個叫做 GetValue
的方法,該方法返回的結果才是對象 o
的 m
屬性所對應的值,也就是上面那個函數。就像上邊引用表述的那樣, Reference
類型的值其實保存了本次取值操作的對象 o
和字符串 'm'
。其存在的目的是保證類似于 typeof o.m
、delete o.m
和 o.m = 'ooxx'
能夠按照預期執行。本質上是因為這些操作都需要依賴 o
這個對象,而不僅僅是其 m
屬性對應的值。
The Reference type is used to explain the behaviour of such operators as delete, typeof, the assignment operators, the super keyword and other language features. For example, the left-hand operand of an assignment is expected to produce a reference.
括號
然后,我們再簡單的看一下括號表達式(The Grouping Operator),其規范中有很重要的一點是:
Return the result of evaluating Expression. This may be of type Reference.
規范還解釋了為什么 :
This algorithm does not apply GetValue to the result of evaluating Expression. The principal motivation for this is so that operators such as delete and typeof may be applied to parenthesized expressions.
如上,官方解釋了為什么不對括號里面的表達式執行結果調用 getValue 方法之后再返回,其實希望保證 delete
和 typeof
這些操作符和括號表達式一起工作的時候能夠符合我們的預期。例如,delete (o.m)
和 delete o.m
將會表現的一致。
結論
綜上,屬性訪問和括號表達式共同決定了 (o.m)()
的表現和 o.m()
表現是一致的。
引申
- 為何
(o.n = o.m)()
會打印false
?
那是因為賦值操作的返回結果是對右側表達式的執行結果調用 getValue
方法,如下:
- Let rval be ? GetValue(rref).
- Perform ? DestructuringAssignmentEvaluation of assignmentPattern using rval as the argument.
- Return rval.
同理,你也應該知道為何 (null, o.m)()
也會打印 false
。
- 為何我在控制臺中輸入
o.m
顯示就是相應的函數而不是一個Reference
類型的值?
那是因為此時表達式會被解釋成一條獨立的語句,因此有如下規范:
- Let exprRef be the result of evaluating Expression.
- Return ? GetValue(exprRef).
這里調用了 GetValue
方法。