JS雙等號比較符(「==」)做用細節

本文發佈於 https://wintc.top/article/50,轉載請註明

都知道JS裏」==「和」===「的區別,在於非同類型變量的比較中,」===「會直接返回false,而」==「則會先將兩個比較值先轉換爲同一類型,再進行比較。然而,這裏」先轉換爲同一類型「是什麼樣的規則呢?javascript

1、容易忽視的比較細節

一直都沒有在乎」比較中的隱式類型轉換「這個問題,由於常見的狀況都太簡單了:java

"1" ==  1;            // true
1 == "1";             // true
0 == false;           // true

很簡單, 很直觀,直覺就是如此。直到我看見下面的比較:面試

![] == [] // true

看到這個比較前,我不知到沒有特殊處理(非劫持、代理等)的a值能使得 a == a && a == !a 會返回true,然而如今它就在這裏:函數

> a = []
[]
> a == a && a == !a
true

是時候該完全掌握」比較中的隱式類型轉換「了。許多教程、書本都建議應該使用」===「,避免使用」==「,以免代碼中的不肯定性以及」===「速度會更快(由於沒有類型轉換)。經典書籍《你不知道的Javascript》一書中的觀點卻非如此,我比較贊同書中的觀點,書中認爲:存在」==「就應該搞清楚它的做用原理而且在代碼中合理使用它,而不是一味避之。對我而言,我極少使用」===「,而且在編碼時避免在不一樣類型變量之間進行比較。測試

2、」==「做用規則

a == b,若是a、b類型相同,那很簡單,值相同即爲true,不一樣即爲false。因此這裏只討論a、b類型不一樣的狀況——雖然應該避免不一樣類型變量相比較,可是弄明白」比較中的隱式類型轉換「卻很是必要。編碼

參照MDN文檔梳理了一下不一樣類型的的值比較的規則:代理

  • 當數字與字符串進行比較時,會嘗試將字符串轉換爲數字值。
  • 若是操做數之一是Boolean,則將布爾操做數轉換爲1或0。
  • 若是操做數之一是對象,另外一個是數字或字符串,會嘗試使用對象的valueOf()toString()方法將對象轉換爲數字或字符串。
  • null == undefined爲true,此外一般狀況下null和undefined與其它對象都不相等。

能夠看到,前三條規則中,都是試圖轉變爲字符串和數字進行比較,在比較中,能夠把布爾值當成數字。回到剛纔的問題,」![] == []「就比較容易理解了,至關於"false == []",有Boolean操做數,先轉爲數字,至關於比較」0 == []「,而」[]「轉爲數字是0,因此返回true。code

3、對象的valueOf和toString,轉換的時候到底用哪一個?

        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

4、更高優先級的轉換函數:ES6對象的Symbol.toPrimitive屬性

除了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「。

5、總結

隱式類型轉換確實是比較容易忽略的問題,畢竟一般不多用獲得。本文介紹的valueOf、toString、Symbol.toPrimitive等函數,均可以干涉到類型轉換的結果。想起之前網上見到的一道面試題:如何讓a == 1 && a == 2 && a == 3同時成立?使用上述幾個函數應該很簡單就能夠解答這個問題。

相關文章
相關標籤/搜索