ECMAScript7規範中的ToPrimitive抽象操做

本文將介紹ECMAScript7規範中的ToPrimitive抽象操做。算法

預備知識

ECMAScript數據類型

ECMAScript數據類型細分爲兩大類數據類型,一種是語言類型,一種是規範類型數組

  • 語言類型是能夠直接被開發人員使用的數據類型;
  • 規範類型表明meta-values(元值),用在算法中描述ECMAScript語言結構和語言類型的語義。它們主要用於規範的說明,不須要被真正地實現。

ECMAScript語言類型一共有7種:this

  • Undefined
  • Null
  • Boolean,布爾類型
  • String,字符串類型
  • Symbol,符號類型
  • Number,數字類型
  • Object,對象類型

原始數據類型是上述UndefinedNullBooleanStringSymbolNumber的統稱,也就是非對象數據類型。
下文涉及到的規範類型只有List,也就是列表,相似於數組,用符號« »表示。lua

@@toPrimitive

Symbol有不少有名的符號,好比@@toPrimitive,也就是Symbol.toPrimitive,這是定義在Symbol對象上的一個屬性。prototype

ToPrimitive(input [, PreferredType])

該抽象操做接受一個參數input和一個可選的參數PreferredType。該抽象操做的目的是把參數input轉化爲非對象數據類型,也就是原始數據類型。若是input能夠同時轉化爲多個原始數據,那麼會優先參考PreferredType的值。轉化過程參照下表:code

參數input的數據類型 結果
Undefined 返回input自身
Null 返回input自身
Boolean 返回input自身
Number 返回input自身
String 返回input自身
Symbol 返回input自身
Object 執行下面的步驟

若是input的數據類型是對象,執行下述步驟:對象

  1. 若是沒有傳入PreferredType參數,讓hint等於"default"
  2. 若是PreferredTypehint String,讓hint等於"string"
  3. 若是PreferredTypehint Number,讓hint等於"number"
  4. exoticToPrim等於GetMethod(input, @@toPrimitive),大概語義就是獲取參數input@@toPrimitive方法;
  5. 若是exoticToPrim不是Undefined,那麼:ip

    1. result等於Call(exoticToPrim, input, « hint »),大概語義就是執行exoticToPrim(hint)
    2. 若是result是原始數據類型,返回result
    3. 拋出類型錯誤的異常;
  6. 若是hint"default",讓hint等於"number"
  7. 返回OrdinaryToPrimitive(input, hint)抽象操做的結果。

OrdinaryToPrimitive(O, hint)

O的數據類型是對象,hint的數據類型是字符串,而且hint的值要麼是"string",要麼是"number"。該抽象操做的步驟以下:原型鏈

  1. 若是hint"string",讓methodNames等於« "toString", "valueOf" »
  2. 若是hint"number",讓methodNames等於« "valueOf", "toString" »
  3. 按順序迭代列表methodNames,對於每個迭代值name開發

    1. method等於Get(O, name),大概語義就是獲取對象Oname值對應的屬性;
    2. 若是method能夠調用,那麼:

      1. method等於Call(method, O),大概語義就是執行method()
      2. 若是result的類型不是對象,返回result
  4. 拋出類型錯誤的異常。

由上述操做步驟可知:

  • 經過ToPrimitive的步驟6可知,當沒有提供可選參數PreferredType的時候,hint會默認爲"number"
  • 經過ToPrimitive的步驟4可知,能夠經過定義@@toPrimitive方法來覆蓋默認行爲,好比規範中定義的Date日期對象和Symbol符號對象都在原型上定義了@@toPrimitive方法。

實踐

可能有人會問,爲何要講解規範中的抽象方法,抽象方法我又用不到。其實否則,這個方法在不少地方都會用到,只是你不知道罷了。下面經過講解幾個實例讓你們加深對它的理解。

'' + [1, 2, 3]

'' + [1, 2, 3] // "1,2,3"

根據規範中的加法操做,對於操做x + y,會調用ToPrimitive(x)ToPrimitive(y)xy轉化爲原始數據類型。上面的例子中''自己就是原始數據類型了,因此返回''自身。[1, 2, 3]是對象類型,而且數組沒有定義@@toPrimitive屬性。由於沒有提供PreferredType,因此在ToPrimitive操做的步驟6中,hint變爲"number",因此OrdinaryToPrimitive中的methodNames« "valueOf", "toString" »

var a = [1, 2, 3]
a.valueOf() // [1, 2, 3],數組a自己
a.toString() // "1,2,3"

由於valueOf返回的是數組a自己,仍是對象類型,因此會繼續調用toString方法,返回了字符串"1,2,3",因此

'' + [1, 2, 3] // => '' + '1,2,3' => '1,2,3'

那麼,若是咱們覆蓋數組原型上的valueOf方法,使得該方法返回一個原始數據類型,那麼結果會是什麼呢?

var a = [1, 2, 3]
a.valueOf = function () {
    console.log('trigger valueOf')
    return 'hello'
}
'' + a //  => '' + 'hello' => 'hello'

覆蓋默認的valueOf以後,調用valueOf會返回原始數據類型。根據OrdinaryToPrimitive3.2.2,這個時候就直接返回了,不會再調用toString方法。同時在控制檯會log"trigger valueOf",也就是說valueOf確實是調用了。
那麼,若是咱們覆蓋數組默認的toString方法,使得該方法返回對象類型,那麼結果會是什麼呢?

var a = [1, 2, 3]
a.toString = function () {
    console.log('trigger toString')
    return this
}
'' + a // Uncaught TypeError: Cannot convert object to primitive value

由於數組原型上的valueOf方法返回對象類型,在上面的例子中,咱們把toString覆蓋了,使它也返回對象類型,那麼就會直接走到OrdinaryToPrimitive的第4步,也就是拋出類型錯誤的異常,不能把對象轉化爲原始數據類型。
在上面咱們提到過能夠經過@@toPrimitive方法來自定義ToPrimitive的行爲,好比下面的例子:

var a = [1, 2, 3]
a[Symbol.toPrimitive] = function () {
    return 'custom'
}
'' + a // => '' + 'custom' => 'custom'

相加操做在調用ToPrimitive的時候沒有提供PreferredType,接下來說一個會優先使用hint String做爲PreferredType的例子:

var a = [1, 2, 3]
a.valueOf = function () {
    console.log('trigger valueOf')
    return 'hello'
}
a.valueOf() // "hello"
a.toString() // "1,2,3"
var obj = {}
obj[a] = 'hello' // obj是{1,2,3: "hello"}

在把變量做爲鍵值使用的時候,會調用ToPrimitive把鍵值轉化爲原始數據類型,而且PreferredType的值是hint String。經過上面的例子也能夠看出來,a.valueOfa.toString的結果都是字符串,可是使用了'1,2,3',也就是使用了a.toString的結果。固然,若是咱們從新定義toString方法,而且返回對象,那麼就會使用valueOf的值了:

var a = [1, 2, 3]
a.valueOf = function () {
    console.log('trigger valueOf')
    return 'hello'
}
a.toString = function () {
    console.log('trigger toString')
    return this
}
var obj = {}
obj[a] = 'hello' // obj是{hello: "hello"}

而且會在控制檯先log"trigger toString",後log"trigger valueOf"。固然,若是這兩個都返回對象,那麼仍是會報錯:

var a = [1, 2, 3] // 使用原型鏈上的valueOf方法
a.toString = function () {
    console.log('trigger toString')
    return this
}
var obj = {}
obj[a] = 'hello' // Uncaught TypeError: Cannot convert object to primitive value

Date

在上面講ToPrimitive的時候,提到Date對象和Symbol對象在原型上定義了@@toPrimitive方法。在ToPrimitive的第6步的操做中,咱們能夠看到當沒有提供PreferredType的時候,優先調用valueOf方法。Date原型上的@@toPrimitive作的事情很是簡單:當沒有提供PreferredType的時候,優先調用toString方法。因此對於上面的操做,Date對象的行爲是不同的:

var a = [1, 2, 3]
a.valueOf = function () {
    return 'hello'
}
a.valueOf() // "hello"
a.toString() // "1,2,3"
'' + a // "hello"
var date = new Date()
date.valueOf() // 1536416960724
date.toString() // "Sat Sep 08 2018 22:29:20 GMT+0800 (中國標準時間)"
'' + date // "Sat Sep 08 2018 22:29:20 GMT+0800 (中國標準時間)"

咱們能夠看到datevalueOf方法和toString方法都返回原始數據類型,可是優先使用了toString方法。

總結

本文主要講解了ToPrimitive抽象操做,以及一些相關的例子,但願你們能有所收穫。若是本文有什麼錯誤或者不嚴謹的地方,歡迎在評論區留言。

相關文章
相關標籤/搜索