參考《你不知道的JavaScript(中卷)》第四章
理解 ToPrimitive 操作 就能理解了JS 中的有點(diǎn)迷的 ==
操作了
// 以下是有點(diǎn)奇怪的 ==
"0" == false; // true
false == 0; // true
false == ""; // true
false == []; // true
"" == 0; // true
"" == []; // true
0 == []; // true
ToPrimitive 操作
抽象操作 ToPrimitive(參見 ES5 規(guī)范 9.1 節(jié))會(huì)首先(通過內(nèi)部操作 DefaultValue邓萨,參見 ES5 規(guī)范 8.12.8 節(jié))檢查該值是否有 valueOf() 方法。
如果有并且返回基本類型值何荚,就使用該值進(jìn)行強(qiáng)制類型轉(zhuǎn)換。如果沒有就使用 toString() 的返回值(如果存在)來進(jìn)行強(qiáng)制類型轉(zhuǎn)換猪杭。
如果 valueOf() 和 toString() 均不返回基本類型值餐塘,會(huì)產(chǎn)生 TypeError 錯(cuò)誤。
什么是 ToNumber
true
轉(zhuǎn)換為 1皂吮, false
轉(zhuǎn)換為 0戒傻。 undefined
轉(zhuǎn)換為 NaN
, null
轉(zhuǎn)換為 0涮较。ToNumber 對(duì)字符串的處理基本遵循數(shù)字常量的相關(guān)規(guī)則 / 語法。處理失敗時(shí)返回 NaN
(處理數(shù)字常量失敗時(shí)會(huì)產(chǎn)生語法錯(cuò)誤)冈止。不同之處是 ToNumber 對(duì)以 0 開頭的十六進(jìn)制數(shù)并不按十六進(jìn)制處理(而是按十進(jìn)制)
== 和 ===
==
允許在相等比較中進(jìn)行強(qiáng)制類型轉(zhuǎn)換狂票,而 ===
不允許。
實(shí)際上 ==
和 ===
都會(huì)檢查操作數(shù)的類型熙暴。區(qū)別在于操作數(shù)類型不同時(shí)它們的處理方式不同闺属。
特別情況
NaN == NaN // false
+0 == -0 // true
ES5 規(guī)范 11.9.3.1 的最后定義了對(duì)象(包括函數(shù)和數(shù)組)的寬松相等(==):兩個(gè)對(duì)象指向同一個(gè)值時(shí)即視為相等,不發(fā)生強(qiáng)制類型轉(zhuǎn)換周霉。
在比較兩個(gè)對(duì)象的時(shí)候掂器, ==
和 ===
的工作原理是一樣的
== 比較不同類型時(shí)發(fā)生了什么
顯然會(huì)進(jìn)行類型轉(zhuǎn)換
基本數(shù)據(jù)類型都轉(zhuǎn)為數(shù)字
對(duì)象類型進(jìn)行 ToPrimitive 操作
轉(zhuǎn)換的優(yōu)先級(jí)是 布爾 > 字符串 > 對(duì)象
最終他們都會(huì)轉(zhuǎn)為 數(shù)字 類型(因?yàn)榛緮?shù)據(jù)類型都會(huì)轉(zhuǎn)為數(shù)字類型,對(duì)象的轉(zhuǎn)換優(yōu)先級(jí)最低俱箱,輪到對(duì)象進(jìn)行轉(zhuǎn)換的時(shí)候国瓮,另外一個(gè)需要轉(zhuǎn)換的操作數(shù)早就轉(zhuǎn)為數(shù)字了,如果 ToPrimitive 操作返回的結(jié)果非數(shù)字,那么要進(jìn)行 == 操作的兩個(gè)操作數(shù)的類型依然不同乃摹, ToPrimitive 操作返回的結(jié)果還需要轉(zhuǎn)為數(shù)字)
舉個(gè)例子禁漓,布爾值和字符串比較:
false == "abc" // false
// 轉(zhuǎn)換的優(yōu)先級(jí)是 布爾 > 字符串,所以 false 先轉(zhuǎn)為數(shù)字得到 0
// 現(xiàn)在相當(dāng)于比較 0 == "abc"孵睬,操作數(shù)的類型還是不同播歼,繼續(xù)類型轉(zhuǎn)換
// "abc" 轉(zhuǎn)為數(shù)字,Number("abc") 得到 NaN
// 0 == NaN 結(jié)果 false
布爾值和對(duì)象比較:
false == [] // true
// 轉(zhuǎn)換的優(yōu)先級(jí)是 布爾 > 對(duì)象掰读,所以 false 先轉(zhuǎn)為數(shù)字得到 0
// 現(xiàn)在相當(dāng)于比較 0 == []秘狞,操作數(shù)的類型還是不同,繼續(xù)類型轉(zhuǎn)換
// [] 是對(duì)象蹈集,進(jìn)行 ToPrimitive 操作烁试,得到空字符,
// 現(xiàn)在相當(dāng)于比較 0 == ""雾狈,操作數(shù)的類型還是不同廓潜,繼續(xù)類型轉(zhuǎn)換
// 空字符是基本數(shù)據(jù)類型,轉(zhuǎn)為數(shù)字善榛,Number("") 得到 0
// 0 == 0 結(jié)果 true
那么 null 和 undefined 呢辩蛋?這里引出另外一個(gè)問題:包裝類型(看最后)
他們不會(huì)進(jìn)行轉(zhuǎn)換
null 只會(huì) == undefined 或者自身,undefined 同樣
null == null // true
null == undefined // true
undefined == undefined // true
對(duì) [] 和 {} 呢移盆?
以下代碼不是說明 [] == {}
發(fā)生了什么悼院,因?yàn)閮蓚€(gè)對(duì)象進(jìn)行寬松或者嚴(yán)格相等時(shí),不進(jìn)行類型轉(zhuǎn)換咒循,兩個(gè)對(duì)象指向同一個(gè)值時(shí)即視為相等
// 對(duì)象和基本數(shù)據(jù)類型進(jìn)行寬松比較時(shí)据途,對(duì)象發(fā)生了什么?
// 當(dāng)對(duì)象是 []
[].valueOf() // 還是一個(gè)對(duì)象
[].toString() // '' 一個(gè)空字符串
Number("") // 0
// 當(dāng)對(duì)象是 [2,3]
[2,3].valueOf() // 還是一個(gè)對(duì)象
[2,3].toString() // "2,3"
Number("2,3") // NaN
// 當(dāng)對(duì)象是 [null]
[null].valueOf() // 還是一個(gè)對(duì)象
[null].toString() // ""
Number([null]) // 0
/*
也許你認(rèn)為 [null].toString() 返回的不是 ""
但是如果不這樣處理的話又能怎樣呢叙甸?
有人也許會(huì)覺得既然 String(null) 返回 "null"
所以 String([null]) 也應(yīng)該返回 "null"颖医。
確實(shí)有道理,實(shí)際上這是 String([..]) 規(guī)則的問題裆蒸。
又或者根本就不應(yīng)該將數(shù)組轉(zhuǎn)換為字符串熔萧?
但這樣一來又會(huì)導(dǎo)致很多其他問題
*/
// 當(dāng)對(duì)象是 {}
({}).valueOf() // 還是一個(gè)對(duì)象
({}).toString() // "[object Object]"
Number( "[object Object]") // NaN
>, <, <=
這屬于抽象關(guān)系比較:
比較雙方首先調(diào)用 ToPrimitive,如果結(jié)果出現(xiàn)非字符串僚祷,就根據(jù) ToNumber 規(guī)則將雙方強(qiáng)制類型轉(zhuǎn)換為數(shù)字來進(jìn)行比較佛致。
實(shí)際上 JavaScript 中 <= 是 “不大于” 的意思(a <= b 被處理為 b < a,然后將結(jié)果反轉(zhuǎn)辙谜。)即 a <= b 俺榆,處理為 !(b < a)。
相等比較有嚴(yán)格相等装哆,關(guān)系比較卻沒有“嚴(yán)格關(guān)系比較”(strict relational comparison)罐脊。也就是說如果要避免 a < b 中發(fā)生隱式強(qiáng)制類型轉(zhuǎn)換定嗓,我們只能確保 a 和 b 為相同的類型,除此之外別無他法爹殊。
包裝類型
基本數(shù)據(jù)類型:number蜕乡、string、boolean 都有包裝類型梗夸,這是為了讓這些基本數(shù)據(jù)類型可以方便地調(diào)用一些常用的方法层玲,比如 toString,valueOf 等等
但是 null 和 undefined 沒有對(duì)應(yīng)的包裝類型反症,所以 null 和 undefined 不能夠被封裝(boxed)
Object(null) 和 Object() 均返回一個(gè)常規(guī)對(duì)象辛块。
“拆封”,即“打開”封裝對(duì)象(如 new String("abc"))铅碍,返回其中的基本數(shù)據(jù)類型值("abc")润绵。
以上說的和 ==
有什么關(guān)系?
因?yàn)?==
中的 ToPromitive 強(qiáng)制類型轉(zhuǎn)換也會(huì)發(fā)生拆封胞谈,這大概就是很多人錯(cuò)誤地認(rèn)為 == 不進(jìn)行類型判斷的原因(我猜的)
var a = "abc";
var b = Object( a ); // 和new String( a )一樣
a === b; // false
a == b; // true
Object(null) 和 Object() 均返回一個(gè)常規(guī)對(duì)象尘盼,沒法拆封。
var a = null;
var b = Object( a ); // 和Object()一樣
a == b; // false
var c = undefined;
var d = Object( c ); // 和Object()一樣
c == d; // false
var e = NaN;
var f = Object( e ); // 和new Number( e )一樣
e == f; // false