最近我讀了 GitHub 上的一個倉庫項目 《What the f*ck JavaScript?》,列舉了 JavaScript 語言上比較怪癖的一些特性。東西比較多,看起來也有點雜。javascript
其中有至關一部份內容涉及到類型轉換——java
// 1. 隱式轉換:使用 `==` 運算符比較
[] == 0 // -> true
// 2. 顯式轉換:好比下面顯式調用了 `toString` 方法,轉成字符串
[].toString() // -> ""
複製代碼
// 1. 基本類型轉換
0 == +false;
// 2. 對象類型轉換
[1, 2, 3] + [4, 5, 6]; // -> '1,2,34,5,6'
複製代碼
最開始咱們接觸這些東西的時候,有些摸不着頭腦,感受很煩的。git
其實裏面是有規則的,也很少。排除掉規則相對簡單的基本類型轉換,總結下來,實際困擾咱們的核心點有兩個:github
什麼時候發生隱式類型轉換?算法
一般發生在比較運算(==
、!=
、>
、<
)和算術運算(+
、-
、*
、/
、%
)中,並且運算符兩邊的操做數不是同一類型。數組
注: 相等運算符
==
有一條特別的規則,在規範中定義——就是null == undefined
的比較結果返回true
。bash
對象的類型轉換流程又是怎樣的?函數
當咱們談及對象的類型轉換時,一般講的是對象到基本類型的轉換。當上述描述的場景發生時,對象轉換規則就開始起做用了。post
規則梳理起來並不複雜,就那麼幾條。由於一年前,我針對對象到基本類型轉換作了簡單的介紹,所以這次文章命名使用了「再談」。ui
此次我會盡可能詳細的給你們介紹轉換內部的機制,帶領你們理解透對象究竟是怎樣轉換的。
對象發生類型轉換時,首先會檢查對象上是否存在 [Symbol.toPrimitive]
屬性,若是存在的話就調用。該屬性存在一些限制:它必須是一個函數,並且返回值必須是基本類型,不然就要報錯:
1). 不是函數
2). 函數返回值不是基本類型
[Symbol.toPrimitive]
在調用時,系統會自動給與一個參數 hint,這個 hint 能夠理解爲,這次對象發生轉換的預期類型爲什麼。
hint 有三種可能取值:'string'
、'number'
和 'default'
。
下面咱們來演示下,這三種狀況的發生場景。
var obj = {
[Symbol.toPrimitive](hint) {
console.log('[Symbol.toPrimitive]', hint)
}
}
undefined
// 第一種狀況(指望類型是 number):
+obj
// [Symbol.toPrimitive] number
// NaN
// 第二種狀況(指望類型是 string):
String(obj)
// [Symbol.toPrimitive] string
// "undefined"
// 第三種狀況(未知):
obj + ':('
// [Symbol.toPrimitive] default
// "undefined:("
obj + 1
// [Symbol.toPrimitive] default
// NaN
複製代碼
[Symbol.toPrimitive]
方法裏咱們沒有定義返回值,所以方法返回值是默認的 undefined
。
+obj
-> +undefined
-> Number(undefined)
-> NaN
。String(obj)
-> String(undefined)
-> "undefined"
。obj + ':('
-> undefined + ':('
-> "undefined:("
。obj + 1
-> undefined + 1
-> NaN
。然而當對象不存在 [Symbol.toPrimitive]
的時候,轉換規則又是怎樣的呢?
這就關係到 Object.prototype
對象上的兩個方法了:valueOf
和 toString
。
當對象上不存在 [Symbol.toPrimitive]
屬性的時候,若發生類型轉換,就要用到 Object.prototype
對象上的 valueOf
和 toString
兩個方法了。
兩個方法調用有前後,可能都會調用,也能夠只調用一個就完成轉換,返回結果。這跟方法返回值和 hint 值有關係。
具體是:
'default'
,則與 hint 爲 'number'
時同樣對待。'number'
,則
valueOf
方法,若是方法返回的是一個基本類型值,則對象的轉換結果就是這個返回值;toString
方法。'string'
,則
toString
方法,若是方法返回的是一個基本類型值,則對象的轉換結果就是這個返回值;valueOf
方法。你可能要問了,若是兩個方法的返回值都是對象的話,豈不是得不到對象最終的轉換結果了?一點都沒錯,咱們來試一下:
上圖裏,咱們在對象 obj
上定義了 valueOf
和 toString
方法,在發生類型轉換時,覆蓋掉原型對象上的同名方法,以便咱們能更加真切地感覺到對象內部的實際的轉換流程。
咱們製造了一個極端狀況,兩個方法都沒有返回基本類型只而是對象,結果呢?而後控制檯就報錯了,告訴咱們不要這樣玩。
須要說明的是——在 JavaScript 中,當咱們在一個對象上調用 valueOf
方法的時候,實際上調用的是 Object.prototype.valueOf
這個原型方法。默認這個方法的返回值始終是調用對象自身,也就是說 valueOf
方法的返回的始終是對象,而非一個基本類型值。
注:
Date 對象除外,由於
Date.prototype
上定義的valueOf
方法覆蓋掉了Object.protortype
上的。在 Date 對象上調用valueOf
方法,返回的是時間對象內部的時間戳表示。另外,
Date.prototype
也定義了本身的[Symbol.toPrimitive]
屬性,默認是不可寫入的。
這就獲得了一個結論:對象發生到基本類型轉換時,最終轉換結果就是 obj.toString()
返回值!這就解釋了下面代碼裏的輸出結果:
var obj = {}
// (1)
obj + ' :)' // "[object Object] :)"
// (2)
obj - 1 // NaN
複製代碼
obj + ' :)'
-> '[object Object]' + :)'
-> '[object Object] :)'
obj - 1
-> '[object Object]' - 1
-> Number('[object Object]') - 1
-> NaN - 1
-> NaN
總結下來,一個對象轉換到基本類型的算法步驟以下:
[Symbol.toPrimitive]
屬性:
'default'
/ 'number'
:
valueOf
方法,若是方法返回的是一個基本類型值,則對象的轉換結果就是這個返回值,toString
方法。'string'
:
toString
方法,若是方法返回的是一個基本類型值,則對象的轉換結果就是這個返回值,valueOf
方法。數組本質也是對象,所以上面的轉換規則也適應於它。不一樣的是數組原型對象上定義了本身的實現方法:Array.prototype.toString
,這個方法覆蓋掉了 Object.prototype
上的同名方法。
重寫以後的 toString
方法的邏輯(相似)以下:
Array.prototype.toString = function () { return this.join() }
複製代碼
由於並未覆蓋 valueOf
方法(也是返回對象自己,對應到這裏就是數組實例),所以數組轉換結果就是調用 array.join()
的返回結果。
下面舉幾個例子:
var emptyArray = []
var array1 = [123]
var array2 = ['hi', 'How are you']
// (1)
Number(emptyArray) // 0
// (2)
array1 - 100 // 23
// (3)
array2 + '?' // "hi,How are you?"
複製代碼
對應的轉換過程以下:
emptyArray
首先會轉換成空字符 ''
,Numbe('')
的結果就是 0
,array1
會轉換成字符串 '123'
,'123' - 100
至關於 Number('123') - 100
,也就是 23
,array2
會轉換成字符串 'hi,How are you'
,'hi,How are you' + '?'
的結果天然是 'hi,How are you?'
。到這裏的話,對象類型轉換的內容就講完了,但願能幫助到你們!
感謝你花費寶貴的時間閱讀這篇文章,但願你能有所收穫。若是你以爲這篇文章讓你的生活美好了一點點,歡迎給我點贊或者評論建議。謝謝!
(完)