本文將介紹ECMAScript7
規範中的ToPrimitive
抽象操做。算法
ECMAScript
數據類型細分爲兩大類數據類型,一種是語言類型,一種是規範類型:數組
meta-values
(元值),用在算法中描述ECMAScript
語言結構和語言類型的語義。它們主要用於規範的說明,不須要被真正地實現。ECMAScript
的語言類型一共有7種:this
原始數據類型是上述Undefined
、Null
、Boolean
、String
、Symbol
和Number
的統稱,也就是非對象數據類型。
下文涉及到的規範類型只有List
,也就是列表,相似於數組,用符號« »
表示。lua
Symbol
有不少有名的符號,好比@@toPrimitive
,也就是Symbol.toPrimitive
,這是定義在Symbol
對象上的一個屬性。prototype
該抽象操做接受一個參數input
和一個可選的參數PreferredType
。該抽象操做的目的是把參數input
轉化爲非對象數據類型,也就是原始數據類型。若是input
能夠同時轉化爲多個原始數據,那麼會優先參考PreferredType
的值。轉化過程參照下表:code
參數input 的數據類型 |
結果 |
---|---|
Undefined | 返回input自身 |
Null | 返回input自身 |
Boolean | 返回input自身 |
Number | 返回input自身 |
String | 返回input自身 |
Symbol | 返回input自身 |
Object | 執行下面的步驟 |
若是input
的數據類型是對象,執行下述步驟:對象
PreferredType
參數,讓hint
等於"default"
;PreferredType
是hint String
,讓hint
等於"string"
;PreferredType
是hint Number
,讓hint
等於"number"
;exoticToPrim
等於GetMethod(input, @@toPrimitive)
,大概語義就是獲取參數input
的@@toPrimitive
方法;若是exoticToPrim
不是Undefined
,那麼:ip
result
等於Call(exoticToPrim, input, « hint »)
,大概語義就是執行exoticToPrim(hint)
;result
是原始數據類型,返回result
;hint
是"default"
,讓hint
等於"number"
;OrdinaryToPrimitive(input, hint)
抽象操做的結果。O
的數據類型是對象,hint
的數據類型是字符串,而且hint
的值要麼是"string"
,要麼是"number"
。該抽象操做的步驟以下:原型鏈
hint
是"string"
,讓methodNames
等於« "toString", "valueOf" »
;hint
是"number"
,讓methodNames
等於« "valueOf", "toString" »
;按順序迭代列表methodNames
,對於每個迭代值name
:開發
method
等於Get(O, name)
,大概語義就是獲取對象O
的name
值對應的屬性;若是method
能夠調用,那麼:
method
等於Call(method, O)
,大概語義就是執行method()
;result
的類型不是對象,返回result
;由上述操做步驟可知:
ToPrimitive
的步驟6
可知,當沒有提供可選參數PreferredType
的時候,hint
會默認爲"number"
;ToPrimitive
的步驟4
可知,能夠經過定義@@toPrimitive
方法來覆蓋默認行爲,好比規範中定義的Date
日期對象和Symbol
符號對象都在原型上定義了@@toPrimitive
方法。可能有人會問,爲何要講解規範中的抽象方法,抽象方法我又用不到。其實否則,這個方法在不少地方都會用到,只是你不知道罷了。下面經過講解幾個實例讓你們加深對它的理解。
'' + [1, 2, 3] // "1,2,3"
根據規範中的加法操做,對於操做x + y
,會調用ToPrimitive(x)
和ToPrimitive(y)
把x
和y
轉化爲原始數據類型。上面的例子中''
自己就是原始數據類型了,因此返回''
自身。[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
會返回原始數據類型。根據OrdinaryToPrimitive
的3.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.valueOf
和a.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
在上面講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 (中國標準時間)"
咱們能夠看到date
的valueOf
方法和toString
方法都返回原始數據類型,可是優先使用了toString
方法。
本文主要講解了ToPrimitive
抽象操做,以及一些相關的例子,但願你們能有所收穫。若是本文有什麼錯誤或者不嚴謹的地方,歡迎在評論區留言。