本文發佈於 https://wintc.top/article/50,轉載請註明
都知道JS裏」==「和」===「的區別,在於非同類型變量的比較中,」===「會直接返回false,而」==「則會先將兩個比較值先轉換爲同一類型,再進行比較。然而,這裏」先轉換爲同一類型「是什麼樣的規則呢?javascript
一直都沒有在乎」比較中的隱式類型轉換「這個問題,由於常見的狀況都太簡單了:java
"1" == 1; // true 1 == "1"; // true 0 == false; // true
很簡單, 很直觀,直覺就是如此。直到我看見下面的比較:面試
![] == [] // true
看到這個比較前,我不知到沒有特殊處理(非劫持、代理等)的a值能使得 a == a && a == !a 會返回true,然而如今它就在這裏:函數
> a = [] [] > a == a && a == !a true
是時候該完全掌握」比較中的隱式類型轉換「了。許多教程、書本都建議應該使用」===「,避免使用」==「,以免代碼中的不肯定性以及」===「速度會更快(由於沒有類型轉換)。經典書籍《你不知道的Javascript》一書中的觀點卻非如此,我比較贊同書中的觀點,書中認爲:存在」==「就應該搞清楚它的做用原理而且在代碼中合理使用它,而不是一味避之。對我而言,我極少使用」===「,而且在編碼時避免在不一樣類型變量之間進行比較。測試
a == b,若是a、b類型相同,那很簡單,值相同即爲true,不一樣即爲false。因此這裏只討論a、b類型不一樣的狀況——雖然應該避免不一樣類型變量相比較,可是弄明白」比較中的隱式類型轉換「卻很是必要。編碼
參照MDN文檔梳理了一下不一樣類型的的值比較的規則:代理
Boolean
,則將布爾操做數轉換爲1或0。valueOf()
和toString()
方法將對象轉換爲數字或字符串。能夠看到,前三條規則中,都是試圖轉變爲字符串和數字進行比較,在比較中,能夠把布爾值當成數字。回到剛纔的問題,」![] == []「就比較容易理解了,至關於"false == []",有Boolean操做數,先轉爲數字,至關於比較」0 == []「,而」[]「轉爲數字是0,因此返回true。code
Object對象在隱式轉換的時候,會嘗試調用valueOf和toString函數,向字符串或者數字轉換。那優先會採用哪個函數的值呢?對象
測試後發現:若是valueOf或者toString返回原始值(」String「、」Number「、」Boolean「、」null「、」undefined「),按valueOf > toString的優先級取得返回值,若返回值是」null「或者」undefined「,比較返回false,不然根據另外一個比較值轉爲字符串或者數字進行比較;若是valueOf和toString均不返回原始值,則比較操做將會報錯!教程
const a = {} a.valueOf = () => 1 a.toString = () => 2 console.log(a == 1, a == 2) // true, false const b = {} b.valueOf = () => null // 優先級高於toString,比較直接返回false b.toString = () => '1' console.log(b == 'null', b == 1, b == '1') // false, false, false const c = {} c.valueOf = () => ([]) // 返回非基本值,將嘗試取toString比較 c.toString = () => '1' console.log(c == 'undefined', c == '1') // false, true const d = {} d.valueOf = () => ([]) // 返回非基本值 d.toString = () => ([]) console.log(d == 'undefined', d == '1') // 比較報錯:不能轉爲原始值
很明顯,根據valueOf > toString的優先級能夠看到,objA == 'abc' 的比較並不一樣於簡單地將objA顯式轉換爲字符串進行比較,即:objA == 'abc' 與 String(objeA) == 'abc' 結果並不必定相同(顯式轉換直接走toString):
const e = {} e.valueOf = () => 1 e.toString = () => '2' console.log(e == 1, e == '1', String(e) == '1', String(e) == '2') // true, true, false, true
除了valueOf、toString函數外,ES6規範提出了Symbol.toPrimitive做爲對象屬性名,其值是一個函數,函數定義了對象轉爲字符串、數字等原始值的返回值,其優先級要高於valueOf、toString。
**Symbol.toPrimitive**
是一個內置的 Symbol 值,它是做爲對象的函數值屬性存在的,當一個對象轉換爲對應的原始值時,會調用此函數。該函數被調用時,會被傳遞一個字符串參數 hint
,表示要轉換到的原始值的預期類型。 hint
參數的取值是 "number"
、"string"
和 "default"
中的任意一個。對於」==「操做符,hint傳遞的值是」default「。
const a = {} a[Symbol.toPrimitive] = (hint) => { if (hint == 'number') return 1 if (hint == 'string') return 2 return 3 } a.valueOf = () => 4 a.toString = () => 5 console.log(a == 1, a == 2, a == 3, a == 4, a == 5) // false, false, true, false, false
若是使用Number或者String強制轉換a,則傳入的」hint「會是」number「或者」string「。
隱式類型轉換確實是比較容易忽略的問題,畢竟一般不多用獲得。本文介紹的valueOf、toString、Symbol.toPrimitive等函數,均可以干涉到類型轉換的結果。想起之前網上見到的一道面試題:如何讓a == 1 && a == 2 && a == 3同時成立?使用上述幾個函數應該很簡單就能夠解答這個問題。