特別說(shuō)明,為便于查閱往踢,文章轉(zhuǎn)自https://github.com/getify/You-Dont-Know-JS
隱含的強(qiáng)制轉(zhuǎn)換
隱含的 強(qiáng)制轉(zhuǎn)換是指這樣的類(lèi)型轉(zhuǎn)換:它們是隱藏的愚铡,由于其他的動(dòng)作隱含地發(fā)生的不明顯的副作用突勇。換句話說(shuō)健芭,任何(對(duì)你)不明顯的類(lèi)型轉(zhuǎn)換都是 隱含的強(qiáng)制轉(zhuǎn)換奸汇。
雖然 明確的 強(qiáng)制轉(zhuǎn)換的目的很明白施符,但是這可能 太過(guò) 明顯 —— 隱含的 強(qiáng)制轉(zhuǎn)換擁有相反的目的:使代碼更難理解。
從表面上來(lái)看擂找,我相信這就是許多關(guān)于強(qiáng)制轉(zhuǎn)換的憤怒的源頭戳吝。絕大多數(shù)關(guān)于“JavaScript強(qiáng)制轉(zhuǎn)換”的抱怨實(shí)際上都指向了(不管他們是否理解它) 隱含的 強(qiáng)制轉(zhuǎn)換。
注意: Douglas Crockford贯涎,"JavaScript: The Good Parts" 的作者听哭,在許多會(huì)議和他的作品中聲稱應(yīng)當(dāng)避免JavaScript強(qiáng)制轉(zhuǎn)換。但看起來(lái)他的意思是 隱含的 強(qiáng)制轉(zhuǎn)換是不好的(以他的意見(jiàn))塘雳。然而陆盘,如果你讀他自己的代碼的話,你會(huì)發(fā)現(xiàn)相當(dāng)多的強(qiáng)制轉(zhuǎn)換的例子败明,明確 和 隱含 都有隘马!事實(shí)上,他的擔(dān)憂主要在于==
操作妻顶,但正如你將在本章中看到的酸员,那只是強(qiáng)制轉(zhuǎn)換機(jī)制的一部分蜒车。
那么,隱含強(qiáng)制轉(zhuǎn)換 是邪惡的嗎沸呐?它很危險(xiǎn)嗎醇王?它是JavaScript設(shè)計(jì)上的缺陷嗎呢燥?我們應(yīng)該盡一切力量避免它嗎崭添?
我打賭大多數(shù)讀者都傾向于踴躍地歡呼,“是的叛氨!”
別那么著急呼渣。聽(tīng)我把話說(shuō)完。
讓我們?cè)?隱含的 強(qiáng)制轉(zhuǎn)換是什么寞埠,和可以是什么這個(gè)問(wèn)題上采取一個(gè)不同的角度屁置,而不是僅僅說(shuō)它是“好的明確強(qiáng)制轉(zhuǎn)換的反面”。這太過(guò)狹隘仁连,而且忽視了一個(gè)重要的微妙細(xì)節(jié)蓝角。
讓我們將 隱含的 強(qiáng)制轉(zhuǎn)換的目的定義為:減少搞亂我們代碼的繁冗,模板代碼饭冬,和/或不必要的實(shí)現(xiàn)細(xì)節(jié)使鹅,不使它們的噪音掩蓋更重要的意圖。
用于簡(jiǎn)化的隱含
在我們進(jìn)入JavaScript以前昌抠,我建議使用某個(gè)理論上是強(qiáng)類(lèi)型的語(yǔ)言的假想代碼來(lái)說(shuō)明一下:
SomeType x = SomeType( AnotherType( y ) )
在這個(gè)例子中患朱,我在y
中有一些任意類(lèi)型的值,想把它轉(zhuǎn)換為SomeType
類(lèi)型炊苫。問(wèn)題是裁厅,這種語(yǔ)言不能從當(dāng)前y
的類(lèi)型直接走到SomeType
。它需要一個(gè)中間步驟侨艾,它首先轉(zhuǎn)換為AnotherType
执虹,然后從AnotherType
轉(zhuǎn)換到SomeType
。
現(xiàn)在唠梨,要是這種語(yǔ)言(或者你可用這種語(yǔ)言創(chuàng)建自己的定義)允許你這么說(shuō)呢:
SomeType x = SomeType( y )
難道一般來(lái)說(shuō)你不會(huì)同意我們簡(jiǎn)化了這里的類(lèi)型轉(zhuǎn)換袋励,降低了中間轉(zhuǎn)換步驟的無(wú)謂的“噪音”嗎?我的意思是姻成,在這段代碼的這一點(diǎn)上插龄,能看到并處理y
先變?yōu)?code>AnotherType然后再變?yōu)?code>SomeType的事實(shí),真的 是很重要的一件事嗎科展?
有些人可能會(huì)爭(zhēng)辯均牢,至少在某些環(huán)境下,是的才睹。但我想我可以做出相同的爭(zhēng)辯說(shuō)徘跪,在許多其他的環(huán)境下甘邀,不管是通過(guò)語(yǔ)言本身的還是我們自己的抽象,這樣的簡(jiǎn)化通過(guò)抽象或隱藏這些細(xì)節(jié) 確實(shí)增強(qiáng)了代碼的可讀性垮庐。
毫無(wú)疑問(wèn)松邪,在幕后的某些地方,那個(gè)中間的步驟依然是發(fā)生的哨查。但如果這樣的細(xì)節(jié)在視野中隱藏起來(lái)逗抑,我們就可以將使y
變?yōu)轭?lèi)型SomeType
作為一個(gè)泛化操作來(lái)推理,并隱藏混亂的細(xì)節(jié)寒亥。
雖然不是一個(gè)完美的類(lèi)比邮府,我要在本章剩余部分爭(zhēng)論的是,JS的 隱含的 強(qiáng)制轉(zhuǎn)換可以被認(rèn)為是給你的代碼提供了一個(gè)類(lèi)似的輔助溉奕。
但是褂傀,很重要的是,這不是一個(gè)無(wú)邊界的加勤,絕對(duì)的論斷仙辟。絕對(duì)有許多 邪惡的東西 潛伏在 隱含 強(qiáng)制轉(zhuǎn)換周?chē)鼈儗?duì)你的代碼造成的損害要比任何潛在的可讀性改善厲害的多鳄梅。很清楚叠国,我們不得不學(xué)習(xí)如何避免這樣的結(jié)構(gòu),使我們不會(huì)用各種bug來(lái)毒害我們的代碼卫枝。
許多開(kāi)發(fā)者相信煎饼,如果一個(gè)機(jī)制可以做某些有用的事兒 A,但也可以被濫用或誤用來(lái)做某些可怕的事兒 Z校赤,那么我們就應(yīng)當(dāng)將這種機(jī)制整個(gè)兒扔掉吆玖,僅僅是為了安全。
我對(duì)你的鼓勵(lì)是:不要安心于此马篮。不要“把孩子跟洗澡水一起潑出去”沾乘。不要因?yàn)槟阒灰?jiàn)到過(guò)它的“壞的一面”就假設(shè) 隱含 強(qiáng)制轉(zhuǎn)換都是壞的。我認(rèn)為這里有“好的一面”浑测,而我想要幫助和啟發(fā)你們更多的人找到并接納它們翅阵!
隱含地:Strings <--> Numbers
在本章的早先,我們探索了string
和number
值之間的 明確 強(qiáng)制轉(zhuǎn)換∏ㄑ耄現(xiàn)在掷匠,讓我們使用 隱含 強(qiáng)制轉(zhuǎn)換的方式探索相同的任務(wù)。但在我們開(kāi)始之前岖圈,我們不得不檢視一些將會(huì) 隱含地 發(fā)生強(qiáng)制轉(zhuǎn)換的操作的微妙之處讹语。
為了服務(wù)于number
的相加和string
的連接兩個(gè)目的,+
操作符被重載了蜂科。那么JS如何知道你想用的是哪一種操作呢顽决?考慮下面的代碼:
var a = "42";
var b = "0";
var c = 42;
var d = 0;
a + b; // "420"
c + d; // 42
是什么不同導(dǎo)致了"420"
和42
短条?一個(gè)常見(jiàn)的誤解是,這個(gè)不同之處在于操作數(shù)之一或兩者是否是一個(gè)string
才菠,這意味著+
將假設(shè)string
連接茸时。雖然這有一部分是對(duì)的,但實(shí)際情況要更復(fù)雜赋访。
考慮如下代碼:
var a = [1,2];
var b = [3,4];
a + b; // "1,23,4"
兩個(gè)操作數(shù)都不是string
可都,但很明顯它們都被強(qiáng)制轉(zhuǎn)換為string
然后啟動(dòng)了string
連接。那么到底發(fā)生了什么进每?
(警告: 語(yǔ)言規(guī)范式的深度細(xì)節(jié)就要來(lái)了汹粤,如果這會(huì)嚇到你就跳過(guò)下面兩段C)
根據(jù)ES5語(yǔ)言規(guī)范的11.6.1部分田晚,+
的算法是(當(dāng)一個(gè)操作數(shù)是object
值時(shí)),如果兩個(gè)操作數(shù)之一已經(jīng)是一個(gè)string
国葬,或者下列步驟產(chǎn)生一個(gè)string
表達(dá)形式贤徒,+
將會(huì)進(jìn)行連接。所以汇四,當(dāng)+
的兩個(gè)操作數(shù)之一收到一個(gè)object
(包括array
)時(shí)接奈,它首先在這個(gè)值上調(diào)用ToPrimitive
抽象操作(9.1部分),而它會(huì)帶著number
的上下文環(huán)境提示來(lái)調(diào)用[[DefaultValue]]
算法(8.12.8部分)通孽。
如果你仔細(xì)觀察序宦,你會(huì)發(fā)現(xiàn)這個(gè)操作現(xiàn)在和ToNumber
抽象操作處理object
的過(guò)程是一樣的(參見(jiàn)早先的“ToNumber
”一節(jié))。在array
上的valueOf()
操作將會(huì)在產(chǎn)生一個(gè)簡(jiǎn)單基本類(lèi)型時(shí)失敗背苦,于是它退回到一個(gè)toString()
表現(xiàn)形式互捌。兩個(gè)array
因此分別變成了"1,2"
和"3,4"
。現(xiàn)在行剂,+
就如你通常期望的那樣連接這兩個(gè)string
:"1,23,4"
。
讓我們把這些亂七八糟的細(xì)節(jié)放在一邊,回到一個(gè)早前的菩掏,簡(jiǎn)化的解釋?zhuān)喝绻?code>+的兩個(gè)操作數(shù)之一是一個(gè)string
(或在上面的步驟中成為一個(gè)string
)鲫竞,那么操作就會(huì)是string
連接。否則铲觉,它總是數(shù)字加法澈蝙。
注意: 關(guān)于強(qiáng)制轉(zhuǎn)換,一個(gè)經(jīng)常被引用的坑是[] + {}
和{} + []
撵幽,這兩個(gè)表達(dá)式的結(jié)果分別是"[object Object]"
和0
灯荧。雖然對(duì)此有更多的東西,但是我們將在第五章的“Block”中講解這其中的細(xì)節(jié)并齐。
這對(duì) 隱含 強(qiáng)制轉(zhuǎn)換意味著什么漏麦?
你可以簡(jiǎn)單地通過(guò)將number
和空string``""
“相加”來(lái)把一個(gè)number
強(qiáng)制轉(zhuǎn)換為一個(gè)string
:
var a = 42;
var b = a + "";
b; // "42"
提示: 使用+
操作符的數(shù)字加法是可交換的客税,這意味著2 + 3
與3 + 2
是相同的。使用+
的字符串連接很明顯通常不是可交換的撕贞,但是 對(duì)于""
的特定情況更耻,它實(shí)質(zhì)上是可交換的,因?yàn)?code>a + ""和"" + a
會(huì)產(chǎn)生相同的結(jié)果捏膨。
使用一個(gè)+ ""
操作將number
(隱含地)強(qiáng)制轉(zhuǎn)換為string
是極其常見(jiàn)/慣用的秧均。事實(shí)上,有趣的是号涯,一些在口頭上批評(píng) 隱含 強(qiáng)制轉(zhuǎn)換得最嚴(yán)厲的人仍然在他們自己的代碼中使用這種方式目胡,而不是使用它的 明確的 替代形式。
在 隱含 強(qiáng)制轉(zhuǎn)換的有用形式中链快,我認(rèn)為這是一個(gè)很棒的例子誉己,盡管這種機(jī)制那么頻繁地被人詬病域蜗!
將a + ""
這種 隱含的 強(qiáng)制轉(zhuǎn)換與我們?cè)缦鹊?code>String(a)明確的 強(qiáng)制轉(zhuǎn)換的例子相比較巨双,有一個(gè)另外的需要小心的奇怪之處。由于ToPrimitive
抽象操作的工作方式霉祸,a + ""
在值a
上調(diào)用valueOf()
筑累,它的返回值再最終通過(guò)內(nèi)部的ToString
抽象操作轉(zhuǎn)換為一個(gè)string
。但是String(a)
只直接調(diào)用toString()
丝蹭。
兩種方式的最終結(jié)果都是一個(gè)string
慢宗,但如果你使用一個(gè)object
而不是一個(gè)普通的基本類(lèi)型number
的值,你可能不一定得到 相同的 string
值奔穿!
考慮這段代碼:
var a = {
valueOf: function() { return 42; },
toString: function() { return 4; }
};
a + ""; // "42"
String( a ); // "4"
一般來(lái)說(shuō)這樣的坑不會(huì)咬到你镜沽,除非你真的試著創(chuàng)建令人困惑的數(shù)據(jù)結(jié)構(gòu)和操作,但如果你為某些object
同時(shí)定義了你自己的valueOf()
和toString()
方法巫橄,你就應(yīng)當(dāng)小心淘邻,因?yàn)槟銖?qiáng)制轉(zhuǎn)換這些值的方式將影響到結(jié)果。
那么另外一個(gè)方向呢湘换?我們?nèi)绾螌⒁粋€(gè)string
隱含強(qiáng)制轉(zhuǎn)換 為一個(gè)number
宾舅?
var a = "3.14";
var b = a - 0;
b; // 3.14
-
操作符是僅為數(shù)字減法定義的,所以a - 0
強(qiáng)制a
的值被轉(zhuǎn)換為一個(gè)number
彩倚。雖然少見(jiàn)得多筹我,a * 1
或a / 1
也會(huì)得到相同的結(jié)果,因?yàn)檫@些操作符也是僅為數(shù)字操作定義的帆离。
那么對(duì)-
操作符使用object
值會(huì)怎樣呢蔬蕊?和上面的+
的故事相似:
var a = [3];
var b = [1];
a - b; // 2
兩個(gè)array
值都不得不變?yōu)?code>number,但它們首先會(huì)被強(qiáng)制轉(zhuǎn)換為string
(使用意料之中的toString()
序列化)哥谷,然后再?gòu)?qiáng)制轉(zhuǎn)換為number
岸夯,以便-
減法操作可以實(shí)施麻献。
那么,string
和number
值之間的 隱含 強(qiáng)制轉(zhuǎn)換還是你總是在恐怖故事當(dāng)中聽(tīng)到的丑陋怪物嗎猜扮?我個(gè)人不這么認(rèn)為勉吻。
比較b = String(a)
(明確的)和b = a + ""
(隱含的)。我認(rèn)為在你的代碼中會(huì)出現(xiàn)兩種方式都有用的情況旅赢。當(dāng)然b = a + ""
在JS程序中更常見(jiàn)一些齿桃,不管一般意義上 隱含 強(qiáng)制轉(zhuǎn)換的好處或害處的 感覺(jué) 如何,它都提供了自己的用途煮盼。
隱含地:Booleans --> Numbers
我認(rèn)為 隱含 強(qiáng)制轉(zhuǎn)換可以真正閃光的一個(gè)情況是短纵,將特定類(lèi)型的復(fù)雜boolean
邏輯簡(jiǎn)化為簡(jiǎn)單的數(shù)字加法。當(dāng)然僵控,這不是一個(gè)通用的技術(shù)香到,而是一個(gè)特定情況的特定解決方法。
考慮如下代碼:
function onlyOne(a,b,c) {
return !!((a && !b && !c) ||
(!a && b && !c) || (!a && !b && c));
}
var a = true;
var b = false;
onlyOne( a, b, b ); // true
onlyOne( b, a, b ); // true
onlyOne( a, b, a ); // false
這個(gè)onlyOne(..)
工具應(yīng)當(dāng)僅在正好有一個(gè)參數(shù)是true
/truthy時(shí)返回true
喉祭。它在truthy的檢查上使用 隱含的 強(qiáng)制轉(zhuǎn)換养渴,而在其他的地方使用 明確的 強(qiáng)制轉(zhuǎn)換,包括最后的返回值泛烙。
但如果我們需要這個(gè)工具能夠以相同的方式處理四個(gè),五個(gè)翘紊,或者二十個(gè)標(biāo)志值呢蔽氨?很難想象處理所有那些比較的排列組合的代碼實(shí)現(xiàn)。
但這里是boolean
值到number
(很明顯帆疟,0
或1
)的強(qiáng)制轉(zhuǎn)換可以提供巨大幫助的地方:
function onlyOne() {
var sum = 0;
for (var i=0; i < arguments.length; i++) {
// 跳過(guò)falsy值鹉究。與將它們視為0相同,但是避開(kāi)NaN
if (arguments[i]) {
sum += arguments[i];
}
}
return sum == 1;
}
var a = true;
var b = false;
onlyOne( b, a ); // true
onlyOne( b, a, b, b, b ); // true
onlyOne( b, b ); // false
onlyOne( b, a, b, b, b, a ); // false
注意: 當(dāng)讓?zhuān)嗽?code>onlyOne(..)中的for
循環(huán)踪宠,你可以更簡(jiǎn)潔地使用ES5的reduce(..)
工具自赔,但我不想因此而模糊概念。
我們?cè)谶@里做的事情有賴于true
/truthy的強(qiáng)制轉(zhuǎn)換結(jié)果為1
柳琢,并將它們作為數(shù)字加起來(lái)绍妨。sum += arguments[i]
通過(guò) 隱含的 強(qiáng)制轉(zhuǎn)換使這發(fā)生。如果在arguments
列表中有且僅有一個(gè)值為true
柬脸,那么這個(gè)數(shù)字的和將是1
他去,否則和就不是1
而不能使期望的條件成立。
我們當(dāng)然本可以使用 明確的 強(qiáng)制轉(zhuǎn)換:
function onlyOne() {
var sum = 0;
for (var i=0; i < arguments.length; i++) {
sum += Number( !!arguments[i] );
}
return sum === 1;
}
我們首先使用!!arguments[i]
來(lái)將這個(gè)值強(qiáng)制轉(zhuǎn)換為true
或false
倒堕。這樣你就可以像onlyOne( "42", 0 )
這樣傳入非boolean
值了灾测,而且它依然可以如意料的那樣工作(要不然,你將會(huì)得到string
連接垦巴,而且邏輯也不正確)媳搪。
一旦我們確認(rèn)它是一個(gè)boolean
铭段,我們就使用Number(..)
進(jìn)行另一個(gè) 明確的 強(qiáng)制轉(zhuǎn)換來(lái)確保值是0
或1
。
這個(gè)工具的 明確 強(qiáng)制轉(zhuǎn)換形式“更好”嗎秦爆?它確實(shí)像代碼注釋中解釋的那樣避開(kāi)了NaN
的陷阱稠项。但是,這最終要看你的需要鲜结。我個(gè)人認(rèn)為前一個(gè)版本展运,依賴于 隱含的 強(qiáng)制轉(zhuǎn)換更優(yōu)雅(如果你不傳入undefined
或NaN
),而 明確的 版本是一種不必要的繁冗精刷。
但與我們?cè)谶@里討論的幾乎所有東西一樣拗胜,這是一個(gè)主觀判斷。
注意: 不管是 隱含的 還是 明確的 方式怒允,你可以通過(guò)將最后的比較從1
改為2
或5
埂软,來(lái)分別很容易地制造onlyTwo(..)
或onlyFive(..)
。這要比添加一大堆&&
和||
表達(dá)式要簡(jiǎn)單太多了纫事。所以勘畔,一般來(lái)說(shuō),在這種情況下強(qiáng)制轉(zhuǎn)換非常有用丽惶。
隱含地:* --> Boolean
現(xiàn)在炫七,讓我們將注意力轉(zhuǎn)向目標(biāo)為boolean
值的 隱含 強(qiáng)制轉(zhuǎn)換上,這是目前最常見(jiàn)钾唬,并且還是目前潛在的最麻煩的一種万哪。
記住,隱含的 強(qiáng)制轉(zhuǎn)換是當(dāng)你以強(qiáng)制一個(gè)值被轉(zhuǎn)換的方式使用這個(gè)值時(shí)才啟動(dòng)的抡秆。對(duì)于數(shù)字和string
操作奕巍,很容易就能看出這種強(qiáng)制轉(zhuǎn)換是如何發(fā)生的。
但是儒士,哪個(gè)種類(lèi)的表達(dá)式操作(隱含地)要求/強(qiáng)制一個(gè)boolean
轉(zhuǎn)換呢的止?
- 在一個(gè)
if (..)
語(yǔ)句中的測(cè)試表達(dá)式。 - 在一個(gè)
for ( .. ; .. ; .. )
頭部的測(cè)試表達(dá)式(第二個(gè)子句)着撩。 - 在
while (..)
和do..while(..)
循環(huán)中的測(cè)試表達(dá)式诅福。 - 在
? :
三元表達(dá)式中的測(cè)試表達(dá)式(第一個(gè)子句)。 -
||
(“邏輯或”)和&&
(“邏輯與”)操作符左手邊的操作數(shù)(它用作測(cè)試表達(dá)式 —— 見(jiàn)下面的討論6米谩)权谁。
在這些上下文環(huán)境中使用的,任何還不是boolean
的值憋沿,將通過(guò)本章早先講解的ToBoolean
抽象操作的規(guī)則旺芽,被 隱含地 強(qiáng)制轉(zhuǎn)換為一個(gè)boolean
。
我們來(lái)看一些例子:
var a = 42;
var b = "abc";
var c;
var d = null;
if (a) {
console.log( "yep" ); // yep
}
while (c) {
console.log( "nope, never runs" );
}
c = d ? a : b;
c; // "abc"
if ((a && d) || c) {
console.log( "yep" ); // yep
}
在所有這些上下文環(huán)境中,非boolean
值被 隱含地強(qiáng)制轉(zhuǎn)換 為它們的boolean
等價(jià)物采章,來(lái)決定測(cè)試的結(jié)果运嗜。
||
和&&
操作符
很可能你已經(jīng)在你用過(guò)的大多數(shù)或所有其他語(yǔ)言中見(jiàn)到過(guò)||
(“邏輯或”)和&&
(“邏輯與”)操作符了。所以假設(shè)它們?cè)贘avaScript中的工作方式和其他類(lèi)似的語(yǔ)言基本上相同是很自然的悯舟。
這里有一個(gè)鮮為人知的担租,但很重要的,微妙細(xì)節(jié)抵怎。
其實(shí)奋救,我會(huì)爭(zhēng)辯這些操作符甚至不應(yīng)當(dāng)被稱為“邏輯__操作符”,因?yàn)檫@樣的名稱沒(méi)有完整地描述它們?cè)谧鍪裁捶刺琛H绻屛医o它們一個(gè)更準(zhǔn)確的(也更蹩腳的)名稱尝艘,我會(huì)叫它們“選擇器操作符”或更完整的,“操作數(shù)選擇器操作符”姿染。
為什么背亥?因?yàn)樵贘avaScript中它們實(shí)際上不會(huì)得出一個(gè) 邏輯 值(也就是boolean
),這與它們?cè)谄渌恼Z(yǔ)言中的表現(xiàn)不同悬赏。
那么它們到底得出什么狡汉?它們得出兩個(gè)操作數(shù)中的一個(gè)(而且僅有一個(gè))。換句話說(shuō)闽颇,它們?cè)趦蓚€(gè)操作數(shù)的值中選擇一個(gè)盾戴。
引用ES5語(yǔ)言規(guī)范的11.11部分:
一個(gè)&&或||操作符產(chǎn)生的值不見(jiàn)得是Boolean類(lèi)型。這個(gè)產(chǎn)生的值將總是兩個(gè)操作數(shù)表達(dá)式其中之一的值进萄。
讓我們展示一下:
var a = 42;
var b = "abc";
var c = null;
a || b; // 42
a && b; // "abc"
c || b; // "abc"
c && b; // null
等一下捻脖,什么!中鼠? 想一想。在像C和PHP這樣的語(yǔ)言中沿癞,這些表達(dá)式結(jié)果為true
或false
援雇,而在JS中(就此而言還有Python和Ruby!)椎扬,結(jié)果來(lái)自于值本身惫搏。
||
和&&
操作符都在 第一個(gè)操作數(shù)(a
或c
) 上進(jìn)行boolean
測(cè)試。如果這個(gè)操作數(shù)還不是boolean
(就像在這里一樣)蚕涤,就會(huì)發(fā)生一次普通的ToBoolean
強(qiáng)制轉(zhuǎn)換筐赔,這樣測(cè)試就可以進(jìn)行了。
對(duì)于||
操作符揖铜,如果測(cè)試結(jié)果為true
茴丰,||
表達(dá)式就將 第一個(gè)操作數(shù) 的值(a
或c
)作為結(jié)果。如果測(cè)試結(jié)果為false
,||
表達(dá)式就將 第二個(gè)操作數(shù) 的值(b
)作為結(jié)果贿肩。
相反地峦椰,對(duì)于&&
操作符,如果測(cè)試結(jié)果為true
汰规,&&
表達(dá)式將 第二個(gè)操作數(shù) 的值(b
)作為結(jié)果汤功。如果測(cè)試結(jié)果為false
,那么&&
表達(dá)式就將 第一個(gè)操作數(shù) 的值(a
或c
)作為結(jié)果溜哮。
||
或&&
表達(dá)式的結(jié)果總是兩個(gè)操作數(shù)之一的底層值滔金,不是(可能是被強(qiáng)制轉(zhuǎn)換來(lái)的)測(cè)試的結(jié)果。在c && b
中茂嗓,c
是null
餐茵,因此是falsy。但是&&
表達(dá)式本身的結(jié)果為null
(c
中的值)在抛,不是用于測(cè)試的強(qiáng)制轉(zhuǎn)換來(lái)的false
钟病。
現(xiàn)在你明白這些操作符如何像“操作數(shù)選擇器”一樣工作了嗎?
另一種考慮這些操作數(shù)的方式是:
a || b;
// 大體上等價(jià)于:
a ? a : b;
a && b;
// 大體上等價(jià)于:
a ? b : a;
注意: 我說(shuō)a || b
“大體上等價(jià)”于a ? a : b
刚梭,是因?yàn)殡m然結(jié)果相同肠阱,但是這里有一個(gè)微妙的不同。在a ? a : b
中朴读,如果a
是一個(gè)更復(fù)雜的表達(dá)式(例如像調(diào)用function
那樣可能帶有副作用)屹徘,那么這個(gè)表達(dá)式a
將有可能被求值兩次(如果第一次求值的結(jié)果為truthy)。相比之下衅金,對(duì)于a || b
噪伊,表達(dá)式a
僅被求值一次,而且這個(gè)值將被同時(shí)用于強(qiáng)制轉(zhuǎn)換測(cè)試和結(jié)果值(如果合適的話)氮唯。同樣的區(qū)別也適用于a && b
和a ? b : a
表達(dá)式鉴吹。
很有可能你在沒(méi)有完全理解之前你就已經(jīng)使用了這個(gè)行為的一個(gè)極其常見(jiàn),而且很有幫助的用法:
function foo(a,b) {
a = a || "hello";
b = b || "world";
console.log( a + " " + b );
}
foo(); // "hello world"
foo( "yeah", "yeah!" ); // "yeah yeah!"
這種a = a || "hello"
慣用法(有時(shí)被說(shuō)成C#“null合并操作符”的JavaScript版本)對(duì)a
進(jìn)行測(cè)試惩琉,如果它沒(méi)有值(或僅僅是一個(gè)不期望的falsy值)豆励,就提供一個(gè)后備的默認(rèn)值("hello"
)。
但是 要小心瞒渠!
foo( "That's it!", "" ); // "That's it! world" <-- Oops!
看到問(wèn)題了嗎良蒸?作為第二個(gè)參數(shù)的""
是一個(gè)falsy值(參見(jiàn)本章早先的ToBoolean
),所以b = b || "world"
測(cè)試失敗伍玖,而默認(rèn)值"world"
被替換上來(lái)嫩痰,即便本來(lái)的意圖可能是想讓明確傳入的""
作為賦給b
的值。
這種||
慣用法極其常見(jiàn)窍箍,而且十分有用串纺,但是你不得不只在 所有的falsy值 應(yīng)當(dāng)被跳過(guò)時(shí)使用它丽旅。不然,你就需要在你的測(cè)試中更加具體造垛,而且可能應(yīng)該使用一個(gè)? :
三元操作符魔招。
這種默認(rèn)值賦值慣用法是如此常見(jiàn)(和有用!)五辽,以至于那些公開(kāi)激烈誹謗JavaScript強(qiáng)制轉(zhuǎn)換的人都經(jīng)常在它們的代碼中使用办斑!
那么&&
呢?
有另一種在手動(dòng)編寫(xiě)中不那么常見(jiàn)杆逗,而在JS壓縮器中頻繁使用的慣用法乡翅。&&
操作符會(huì)“選擇”第二個(gè)操作數(shù),當(dāng)且僅當(dāng)?shù)谝粋€(gè)操作數(shù)測(cè)試為truthy罪郊,這種用法有時(shí)被稱為“守護(hù)操作符”(參見(jiàn)第五章的“短接”) —— 第一個(gè)表達(dá)式的測(cè)試“守護(hù)”著第二個(gè)表達(dá)式:
function foo() {
console.log( a );
}
var a = 42;
a && foo(); // 42
foo()
僅在a
測(cè)試為truthy時(shí)會(huì)被調(diào)用蠕蚜。如果這個(gè)測(cè)試失敗,這個(gè)a && foo()
表達(dá)式語(yǔ)句將會(huì)無(wú)聲地停止 —— 這被稱為“短接” —— 而且永遠(yuǎn)不會(huì)調(diào)用foo()
悔橄。
重申一次靶累,幾乎很少有人手動(dòng)編寫(xiě)這樣的東西。通常癣疟,他們會(huì)寫(xiě)if (a) { foo(); }
挣柬。但是JS壓縮器選擇a && foo()
是因?yàn)樗痰亩唷K跃χ浚F(xiàn)在邪蛔,如果你不得不解讀這樣的代碼,你就知道它是在做什么以及為什么了扎狱。
好了侧到,那么||
和&&
在它們的功能上有些不錯(cuò)的技巧,只要你樂(lè)意讓 隱含的 強(qiáng)制轉(zhuǎn)換摻和進(jìn)來(lái)淤击。
注意: a = b || "something"
和a && b()
兩種慣用法都依賴于短接行為匠抗,我們將在第五章中講述它的細(xì)節(jié)。
現(xiàn)在污抬,這些操作符實(shí)際上不會(huì)得出true
和false
的事實(shí)可能使你的頭腦有點(diǎn)兒混亂戈咳。你可能想知道,如果你的if
語(yǔ)句和for
循環(huán)包含a && (b || c)
這樣的復(fù)合的邏輯表達(dá)式壕吹,它們到底都是怎么工作的。
別擔(dān)心删铃!天沒(méi)塌下來(lái)耳贬。你的代碼(可能)沒(méi)有問(wèn)題。你只是可能從來(lái)沒(méi)有理解在這個(gè)符合表達(dá)式被求值 之后猎唁,有一個(gè)向boolean
隱含的 強(qiáng)制轉(zhuǎn)換發(fā)生了咒劲。
考慮這段代碼:
var a = 42;
var b = null;
var c = "foo";
if (a && (b || c)) {
console.log( "yep" );
}
這段代碼將會(huì)像你總是認(rèn)為的那樣工作,除了一個(gè)額外的微妙細(xì)節(jié)。a && (b || c)
的結(jié)果 實(shí)際上 是"foo"
腐魂,不是true
帐偎。所以,這之后if
語(yǔ)句強(qiáng)制值"foo"
轉(zhuǎn)換為一個(gè)boolean
蛔屹,這理所當(dāng)然地將是true
削樊。
看到了?沒(méi)有理由驚慌兔毒。你的代碼可能依然是安全的漫贞。但是現(xiàn)在關(guān)于它在做什么和如何做,你知道了更多育叁。
而且現(xiàn)在你理解了這樣的代碼使用 隱含的 強(qiáng)制轉(zhuǎn)換迅脐。如果你依然屬于“避開(kāi)(隱含)強(qiáng)制轉(zhuǎn)換陣營(yíng)”,那么你就需要退回去并使所有這些測(cè)試 明確:
if (!!a && (!!b || !!c)) {
console.log( "yep" );
}
祝你好運(yùn)豪嗽!...對(duì)不起谴蔑,只是逗個(gè)樂(lè)兒。
Symbol 強(qiáng)制轉(zhuǎn)換
在此為止龟梦,在 明確的 和 隱含的 強(qiáng)制轉(zhuǎn)換之間幾乎沒(méi)有可以觀察到的結(jié)果上的不同 —— 只有代碼的可讀性至關(guān)重要隐锭。
但是ES6的Symbol在強(qiáng)制轉(zhuǎn)換系統(tǒng)中引入了一個(gè)我們需要簡(jiǎn)單討論的坑。由于一個(gè)明顯超出了我們將在本書(shū)中討論的范圍的原因变秦,從一個(gè)symbol
到一個(gè)string
的 明確 強(qiáng)制轉(zhuǎn)換是允許的成榜,但是相同的 隱含 強(qiáng)制轉(zhuǎn)換是不被允許的,而且會(huì)拋出一個(gè)錯(cuò)誤蹦玫。
考慮如下代碼:
var s1 = Symbol( "cool" );
String( s1 ); // "Symbol(cool)"
var s2 = Symbol( "not cool" );
s2 + ""; // TypeError
symbol
值根本不能強(qiáng)制轉(zhuǎn)換為number
(不論哪種方式都拋出錯(cuò)誤)赎婚,但奇怪的是它們既可以 明確地 也可以 隱含地 強(qiáng)制轉(zhuǎn)換為boolean
(總是true
)。
一致性總是容易學(xué)習(xí)的樱溉,而對(duì)付例外從來(lái)就不有趣挣输,但是我們只需要在ES6symbol
值和我們?nèi)绾螐?qiáng)制轉(zhuǎn)換它們的問(wèn)題上多加小心。
好消息:你需要強(qiáng)制轉(zhuǎn)換一個(gè)symbol
值的情況可能極其少見(jiàn)福贞。它們典型的被使用的方式(見(jiàn)第三章)可能不會(huì)用到強(qiáng)制轉(zhuǎn)換撩嚼。
寬松等價(jià)與嚴(yán)格等價(jià)
寬松等價(jià)是==
操作符,而嚴(yán)格等價(jià)是===
操作符挖帘。兩個(gè)操作符都被用于比較兩個(gè)值的“等價(jià)性”完丽,但是“寬松”和“嚴(yán)格”暗示著它們行為之間的一個(gè) 非常重要 的不同,特別是在它們?nèi)绾螞Q定“等價(jià)性”上拇舀。
關(guān)于這兩個(gè)操作符的一個(gè)非常常見(jiàn)的誤解是:“==
檢查值的等價(jià)性逻族,而===
檢查值和類(lèi)型的等價(jià)性〗颈溃”雖然這聽(tīng)起來(lái)很好很合理聘鳞,但是不準(zhǔn)確薄辅。無(wú)數(shù)知名的JavaScript書(shū)籍和文章都是這么說(shuō)的,但不幸的是它們都 錯(cuò)了抠璃。
正確的描述是:“==
允許在等價(jià)性比較中進(jìn)行強(qiáng)制轉(zhuǎn)換站楚,而===
不允許強(qiáng)制轉(zhuǎn)換”。
等價(jià)性的性能
停下來(lái)思考一下第一種(不正確的)解釋和這第二種(正確的)解釋的不同搏嗡。
在第一種解釋中窿春,看起來(lái)===
明顯的要比==
做更多工作,因?yàn)樗€必須檢查類(lèi)型彻况。在第二種解釋中谁尸,==
是要 做更多工作 的,因?yàn)樗坏貌辉陬?lèi)型不同時(shí)走過(guò)強(qiáng)制轉(zhuǎn)換的步驟纽甘。
不要像許多人那樣落入陷阱中良蛮,認(rèn)為這會(huì)與性能有任何關(guān)系,雖然在這個(gè)問(wèn)題上==
好像要比===
慢一些悍赢。強(qiáng)制轉(zhuǎn)換確實(shí)要花費(fèi) 一點(diǎn)點(diǎn) 處理時(shí)間耐亏,但也就是僅僅幾微秒(是的爷辙,1微秒就是一秒的百萬(wàn)分之一G瞅健)褐墅。
如果你比較同類(lèi)型的兩個(gè)值,==
和===
使用的是相同的算法赏迟,所以除了在引擎實(shí)現(xiàn)上的一些微小的區(qū)別屡贺,它們做的應(yīng)當(dāng)是相同的工作。
如果你比較兩個(gè)不同類(lèi)型的值锌杀,性能也不是重要因素甩栈。你應(yīng)當(dāng)問(wèn)自己的是:當(dāng)比較這兩個(gè)值時(shí),我想要進(jìn)行強(qiáng)制轉(zhuǎn)換嗎糕再?
如果你想要進(jìn)行強(qiáng)制轉(zhuǎn)換量没,使用==
寬松等價(jià),但如果你不想進(jìn)行強(qiáng)制轉(zhuǎn)換突想,就使用===
嚴(yán)格等價(jià)殴蹄。
注意: 這里暗示==
和===
都會(huì)檢查它們的操作數(shù)的類(lèi)型。不同之處在于它們?cè)陬?lèi)型不同時(shí)如何反應(yīng)猾担。
抽象等價(jià)性
在ES5語(yǔ)言規(guī)范的11.9.3部分中袭灯,==
操作符的行為被定義為“抽象等價(jià)性比較算法”。那里列出了一個(gè)詳盡但簡(jiǎn)單的算法绑嘹,它明確地指出了類(lèi)型的每一種可能的組合妓蛮,與對(duì)于每一種組合強(qiáng)制轉(zhuǎn)化應(yīng)當(dāng)如何發(fā)生(如果有必要的話)。
警告: 當(dāng)(隱含的)強(qiáng)制轉(zhuǎn)換被中傷為太過(guò)復(fù)雜和缺陷過(guò)多而不能成為 有用的圾叼,好的部分 時(shí)蛤克,遭到譴責(zé)的正是這些“抽象等價(jià)”規(guī)則。一般上夷蚊,它們被認(rèn)為對(duì)于開(kāi)發(fā)者來(lái)說(shuō)過(guò)于復(fù)雜和不直觀而不能實(shí)際學(xué)習(xí)和應(yīng)用构挤,而且在JS程序中,和改善代碼的可讀性比起來(lái)惕鼓,它傾向于導(dǎo)致更多的bug筋现。我相信這是一種有缺陷的預(yù)斷 —— 讀者都是整天都在寫(xiě)(而且讀,理解)算法(也就是代碼)的能干的開(kāi)發(fā)者箱歧。所以矾飞,接下來(lái)的是用簡(jiǎn)單的詞語(yǔ)來(lái)直白地解讀“抽象等價(jià)性”。但我懇請(qǐng)你也去讀一下ES5規(guī)范的11.9.3部分呀邢。我想你將會(huì)對(duì)它是多么合理而感到震驚洒沦。
基本上,它的第一個(gè)條款(11.9.3.1)是在說(shuō)价淌,如果兩個(gè)被比較的值是同一類(lèi)型申眼,它們就像你期望的那樣通過(guò)等價(jià)性簡(jiǎn)單自然地比較。比如蝉衣,42
只和42
相等括尸,而"abc"
只和"abc"
相等。
在一般期望的結(jié)果中病毡,有一些例外需要小心:
-
NaN
永遠(yuǎn)不等于它自己(見(jiàn)第二章) -
+0
和-0
是相等的(見(jiàn)第二章)
條款11.9.3.1的最后一個(gè)規(guī)定是關(guān)于object
(包括function
和array
)的==
寬松相等性比較濒翻。這樣的兩個(gè)值僅在它們引用 完全相同的值 時(shí) 相等。這里沒(méi)有強(qiáng)制轉(zhuǎn)換發(fā)生啦膜。
注意: ===
嚴(yán)格等價(jià)比較與11.9.3.1的定義一模一樣有送,包括關(guān)于兩個(gè)object
的值的規(guī)定。很少有人知道功戚,在兩個(gè)object
被比較的情況下娶眷,==
和===
的行為相同!
11.9.3算法中的剩余部分指出啸臀,如果你使用==
寬松等價(jià)來(lái)比較兩個(gè)不同類(lèi)型的值届宠,它們兩者或其中之一將需要被 隱含地 強(qiáng)制轉(zhuǎn)換。由于這個(gè)強(qiáng)制轉(zhuǎn)換乘粒,兩個(gè)值最終歸于同一類(lèi)型豌注,可以使用簡(jiǎn)單的值的等價(jià)性來(lái)直接比較它們相等與否。
注意: !=
寬松不等價(jià)操作是如你預(yù)料的那樣定義的灯萍,它差不多就是==
比較操作完整實(shí)施轧铁,之后對(duì)結(jié)果取反。這對(duì)于!==
嚴(yán)格不等價(jià)操作也是一樣的旦棉。
比較:string
與number
為了展示==
強(qiáng)制轉(zhuǎn)換齿风,首先讓我們建立本章中早先的string
和number
的例子:
var a = 42;
var b = "42";
a === b; // false
a == b; // true
我們所預(yù)料的药薯,a === b
失敗了,因?yàn)椴辉试S強(qiáng)制轉(zhuǎn)換救斑,而且值42
和"42"
確實(shí)是不同的童本。
然而,第二個(gè)比較a == b
使用了寬松等價(jià)脸候,這意味著如果類(lèi)型偶然不同穷娱,這個(gè)比較算法將會(huì)對(duì)兩個(gè)或其中一個(gè)值實(shí)施 隱含的 強(qiáng)制轉(zhuǎn)換。
那么這里發(fā)生的究竟是那種強(qiáng)制轉(zhuǎn)換呢运沦?是a
的值變成了一個(gè)string
泵额,還是b
的值"42"
變成了一個(gè)number
?
在ES5語(yǔ)言規(guī)范中携添,條款11.9.3.4-5說(shuō):
- 如果Type(x)是Number而Type(y)是String嫁盲,
返回比較x == ToNumber(y)的結(jié)果。- 如果Type(x)是String而Type(y)是Number薪寓,
返回比較ToNumber(x) == y的結(jié)果亡资。
警告: 語(yǔ)言規(guī)范中使用Number
和String
作為類(lèi)型的正式名稱,雖然這本書(shū)中偏好使用number
和string
指代基本類(lèi)型向叉。別讓語(yǔ)言規(guī)范中首字母大寫(xiě)的Number
與Number()
原生函數(shù)把你給搞糊涂了锥腻。對(duì)于我們的目的來(lái)說(shuō),類(lèi)型名稱的首字母大寫(xiě)是無(wú)關(guān)緊要的 —— 它們基本上是同一個(gè)意思母谎。
顯然瘦黑,語(yǔ)言規(guī)范說(shuō)為了比較,將值"42"
強(qiáng)制轉(zhuǎn)換為一個(gè)number
奇唤。這個(gè)強(qiáng)制轉(zhuǎn)換如何進(jìn)行已經(jīng)在前面將結(jié)過(guò)了幸斥,明確地說(shuō)就是通過(guò)ToNumber
抽象操作。在這種情況下十分明顯咬扇,兩個(gè)值42
是相等的甲葬。
比較:任何東西與boolean
當(dāng)你試著將一個(gè)值直接與true
或false
相比較時(shí),你會(huì)遇到==
寬松等價(jià)的 隱含 強(qiáng)制轉(zhuǎn)換中最大的一個(gè)坑懈贺。
考慮如下代碼:
var a = "42";
var b = true;
a == b; // false
等一下经窖,這里發(fā)生了什么!梭灿?我們知道"42"
是一個(gè)truthy值(見(jiàn)本章早先的部分)画侣。那么它和true
怎么不是==
寬松等價(jià)的?
其中的原因既簡(jiǎn)單又刁鉆得使人迷惑堡妒。它是如此的容易讓人誤解配乱,許多JS開(kāi)發(fā)者從來(lái)不會(huì)花費(fèi)足夠多的精力來(lái)完全掌握它。
讓我們?cè)俅我谜Z(yǔ)言規(guī)范,條款11.9.3.6-7
- 如果Type(x)是Boolean搬泥,
返回比較 ToNumber(x) == y 的結(jié)果桑寨。- 如果Type(y)是Boolean,
返回比較 x == ToNumber(y) 的結(jié)果佑钾。
我們來(lái)把它分解西疤。首先:
var x = true;
var y = "42";
x == y; // false
Type(x)
確實(shí)是Boolean
,所以它會(huì)實(shí)施ToNumber(x)
休溶,將true
強(qiáng)制轉(zhuǎn)換為1
。現(xiàn)在扰她,1 == "42"
會(huì)被求值兽掰。這里面的類(lèi)型依然不同,所以(實(shí)質(zhì)上是遞歸地)我們?cè)俅蜗蛟缦戎v解過(guò)的算法求解徒役,它將"42"
強(qiáng)制轉(zhuǎn)換為42
孽尽,而1 == 42
明顯是false
。
反過(guò)來(lái)忧勿,我們?nèi)稳坏玫较嗤慕Y(jié)果:
var x = "42";
var y = false;
x == y; // false
這次Type(y)
是Boolean
杉女,所以ToNumber(y)
給出0
。"42" == 0
遞歸地變?yōu)?code>42 == 0鸳吸,這當(dāng)然是false
熏挎。
換句話說(shuō),值"42"
既不== true
也不== false
晌砾。猛地一看坎拐,這看起來(lái)像句瘋話。一個(gè)值怎么可能既不是truthy也不是falsy呢养匈?
但這就是問(wèn)題所在哼勇!你在問(wèn)一個(gè)完全錯(cuò)誤的問(wèn)題。但這確實(shí)不是你的錯(cuò)呕乎,你的大腦在耍你积担。
"42"
的確是truthy,但是"42" == true
根本就 不是在進(jìn)行一個(gè)boolean測(cè)試/強(qiáng)制轉(zhuǎn)換猬仁,不管你的大腦怎么說(shuō)帝璧,"42"
沒(méi)有 被強(qiáng)制轉(zhuǎn)換為一個(gè)boolean
(true
),而是true
被強(qiáng)制轉(zhuǎn)換為一個(gè)1
逐虚,而后"42"
被強(qiáng)制轉(zhuǎn)換為42
聋溜。
不管我們喜不喜歡,ToBoolean
甚至都沒(méi)參與到這里叭爱,所以"42"
的真假是與==
操作無(wú)關(guān)的撮躁!
而有關(guān)的是要理解==
比較算法對(duì)所有不同類(lèi)型組合如何動(dòng)作。當(dāng)==
的任意一邊是一個(gè)boolean
值時(shí)买雾,boolean
總是首先被強(qiáng)制轉(zhuǎn)換為一個(gè)number
把曼。
如果這對(duì)你來(lái)講很奇怪杨帽,那么你不是一個(gè)人。我個(gè)人建議永遠(yuǎn)嗤军,永遠(yuǎn)注盈,不要在任何情況下,使用== true
或== false
叙赚。永遠(yuǎn)老客。
但時(shí)要記住,我在此說(shuō)的僅與==
有關(guān)震叮。=== true
和=== false
不允許強(qiáng)制轉(zhuǎn)換胧砰,所以它們沒(méi)有ToNumber
強(qiáng)制轉(zhuǎn)換,因而是安全的苇瓣。
考慮如下代碼:
var a = "42";
// 不好(會(huì)失敗的N炯洹):
if (a == true) {
// ..
}
// 也不該(會(huì)失敗的!):
if (a === true) {
// ..
}
// 足夠好(隱含地工作):
if (a) {
// ..
}
// 更好(明確地工作):
if (!!a) {
// ..
}
// 也很好(明確地工作):
if (Boolean( a )) {
// ..
}
如果你在你的代碼中一直避免使用== true
或== false
(也就是與boolean
的寬松等價(jià))击罪,你將永遠(yuǎn)不必?fù)?dān)心這種真/假的思維陷阱哲嘲。
比較:null
與undefined
另一個(gè) 隱含 強(qiáng)制轉(zhuǎn)換的例子可以在null
和undefined
值之間的==
寬松等價(jià)中看到。又再一次引述ES5語(yǔ)言規(guī)范媳禁,條款11.9.3.2-3:
- 如果x是null而y是undefined眠副,返回true。
- 如果x是undefined而y是null损话,返回true侦啸。
當(dāng)使用==
寬松等價(jià)比較null
和undefined
,它們是互相等價(jià)(也就是互相強(qiáng)制轉(zhuǎn)換)的丧枪,而且在整個(gè)語(yǔ)言中不會(huì)等價(jià)于其他值了光涂。
這意味著null
和undefined
對(duì)于比較的目的來(lái)說(shuō),如果你使用==
寬松等價(jià)操作符來(lái)允許它們互相 隱含地 強(qiáng)制轉(zhuǎn)換的話拧烦,它們可以被認(rèn)為是不可區(qū)分的忘闻。
var a = null;
var b;
a == b; // true
a == null; // true
b == null; // true
a == false; // false
b == false; // false
a == ""; // false
b == ""; // false
a == 0; // false
b == 0; // false
null
和undefined
之間的強(qiáng)制轉(zhuǎn)換是安全且可預(yù)見(jiàn)的,而且在這樣的檢查中沒(méi)有其他的值會(huì)給出測(cè)試成立的誤判恋博。我推薦使用這種強(qiáng)制轉(zhuǎn)換來(lái)允許null
和undefined
是不可區(qū)分的齐佳,如此將它們作為相同的值對(duì)待。
比如:
var a = doSomething();
if (a == null) {
// ..
}
a == null
檢查僅在doSomething()
返回null
或者undefined
時(shí)才會(huì)通過(guò)债沮,而在任何其他值的情況下將會(huì)失敗炼吴,即便是0
,false
疫衩,和""
這樣的falsy值硅蹦。
這個(gè)檢查的 明確 形式 —— 不允許任何強(qiáng)制轉(zhuǎn)換 —— (我認(rèn)為)沒(méi)有必要地難看太多了(而且性能可能有點(diǎn)兒不好!):
var a = doSomething();
if (a === undefined || a === null) {
// ..
}
在我看來(lái),a == null
的形式是另一個(gè)用 隱含 強(qiáng)制轉(zhuǎn)換增進(jìn)了代碼可讀性的例子童芹,而且是以一種可靠安全的方式涮瞻。
比較:object
與非object
如果一個(gè)object
/function
/array
被與一個(gè)簡(jiǎn)單基本標(biāo)量(string
,number
假褪,或boolean
)進(jìn)行比較署咽,ES5語(yǔ)言規(guī)范在條款11.9.3.8-9中這樣說(shuō)道:
- 如果Type(x)是一個(gè)String或者Number而Type(y)是一個(gè)Object,
返回比較 x == ToPrimitive(y) 的結(jié)果生音。- 如果Type(x)是一個(gè)Object而Type(y)是String或者Number宁否,
返回比較 ToPrimitive(x) == y 的結(jié)果。
注意: 你可能注意到了缀遍,這些條款僅提到了String
和Number
家淤,而沒(méi)有Boolean
。這是因?yàn)樯桑缥覀冊(cè)缦纫龅模瑮l款11.9.3.6-7首先將任何出現(xiàn)的Boolean
操作數(shù)強(qiáng)制轉(zhuǎn)換為一個(gè)Number
冤寿。
考慮如下代碼:
var a = 42;
var b = [ 42 ];
a == b; // true
值[ 42 ]
的ToPrimitive
抽象操作(見(jiàn)先前的“抽象值操作”部分)被調(diào)用歹苦,結(jié)果為值"42"
。這里它就變?yōu)?code>42 == "42"督怜,我們已經(jīng)講解過(guò)這將變?yōu)?code>42 == 42殴瘦,所以a
和b
被認(rèn)為是強(qiáng)制轉(zhuǎn)換地等價(jià)。
提示: 我們?cè)诒菊略缦扔懻撨^(guò)的ToPrimitive
抽象操作的所以奇怪之處(toString()
号杠,valueOf()
)蚪腋,都在這里如你期望的那樣適用。如果你有一個(gè)復(fù)雜的數(shù)據(jù)結(jié)構(gòu)姨蟋,而且你想在它上面定義一個(gè)valueOf()
方法來(lái)為等價(jià)比較提供一個(gè)簡(jiǎn)單值的話屉凯,這將十分有用。
在第三章中眼溶,我們講解了“拆箱”悠砚,就是一個(gè)基本類(lèi)型值的object
包裝器(例如new String("abc")
這樣的形式)被展開(kāi),其底層的基本類(lèi)型值("abc"
)被返回堂飞。這種行為與==
算法中的ToPrimitive
強(qiáng)制轉(zhuǎn)換有關(guān):
var a = "abc";
var b = Object( a ); // 與`new String( a )`相同
a === b; // false
a == b; // true
a == b
為true
是因?yàn)?code>b通過(guò)ToPrimitive
強(qiáng)制轉(zhuǎn)換為它的底層簡(jiǎn)單基本標(biāo)量值"abc"
灌旧,它與a
中的值是相同的。
然而由于==
算法中的其他覆蓋規(guī)則绰筛,有些值是例外枢泰。考慮如下代碼:
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
值null
和undefined
不能被裝箱 —— 它們沒(méi)有等價(jià)的對(duì)象包裝器 —— 所以Object(null)
就像Object()
一樣铝噩,它們都僅僅產(chǎn)生一個(gè)普通對(duì)象衡蚂。
NaN
可以被封箱到它等價(jià)的Number
對(duì)象包裝器中,當(dāng)==
導(dǎo)致拆箱時(shí),比較NaN == NaN
會(huì)失敗讳窟,因?yàn)?code>NaN永遠(yuǎn)不會(huì)它自己相等(見(jiàn)第二章)让歼。
邊界情況
現(xiàn)在我們已經(jīng)徹底檢視了==
寬松等價(jià)的 隱含 強(qiáng)制轉(zhuǎn)換是如何工作的(從合理與驚訝兩個(gè)方式),讓我們召喚角落中最差勁兒的丽啡,最瘋狂的情況谋右,這樣我們就能看到我們需要避免什么來(lái)防止被強(qiáng)制轉(zhuǎn)換的bug咬到。
首先补箍,讓我們檢視修改內(nèi)建的原生prototype是如何產(chǎn)生瘋狂的結(jié)果的:
一個(gè)擁有其他值的數(shù)字將會(huì)……
Number.prototype.valueOf = function() {
return 3;
};
new Number( 2 ) == 3; // true
警告: 2 == 3
不會(huì)掉到這個(gè)陷阱中改执,這是由于2
和3
都不會(huì)調(diào)用內(nèi)建的Number.prototype.valueOf()
方法,因?yàn)樗鼈円呀?jīng)是基本number
值坑雅,可以直接比較辈挂。然而,new Number(2)
必須通過(guò)ToPrimitive
強(qiáng)制轉(zhuǎn)換裹粤,因此調(diào)用valueOf()
终蒂。
邪惡吧?當(dāng)然遥诉。任何人都不應(yīng)當(dāng)做這樣的事情拇泣。你 可以 這么做,這個(gè)事實(shí)有時(shí)被當(dāng)成批評(píng)強(qiáng)制轉(zhuǎn)換和==
的根據(jù)矮锈。但這種沮喪是被誤導(dǎo)的霉翔。JavaScript不會(huì)因?yàn)槟隳茏鲞@樣的事情而 不好,是 做這樣的事的開(kāi)發(fā)者 不好苞笨。不要陷入“我的編程語(yǔ)言應(yīng)當(dāng)保護(hù)我不受我自己傷害”的謬論债朵。
接下來(lái),讓我們考慮另一個(gè)刁鉆的例子瀑凝,它將前一個(gè)例子的邪惡帶到另一個(gè)水平:
if (a == 2 && a == 3) {
// ..
}
你可能認(rèn)為這是不可能的序芦,因?yàn)?code>a絕不會(huì) 同時(shí) 等于2
和3
。但是“同時(shí)”是不準(zhǔn)確的猜丹,因?yàn)榈谝粋€(gè)表達(dá)式a == 2
嚴(yán)格地發(fā)生在a == 3
之前芝加。
那么,要是我們讓a.valueOf()
在每次被調(diào)用時(shí)擁有一種副作用射窒,使它第一次被調(diào)用時(shí)返回2
而第二次被調(diào)用時(shí)返回3
呢藏杖?很簡(jiǎn)單:
var i = 2;
Number.prototype.valueOf = function() {
return i++;
};
var a = new Number( 42 );
if (a == 2 && a == 3) {
console.log( "Yep, this happened." );
}
重申一次,這些都是邪惡的技巧脉顿。不要這么做蝌麸。也不要用它們來(lái)抱怨強(qiáng)制轉(zhuǎn)換。潛在地濫用一種機(jī)制并不是譴責(zé)這種機(jī)制的充分證據(jù)艾疟。避開(kāi)這些瘋狂的技巧来吩,并堅(jiān)持強(qiáng)制轉(zhuǎn)換的合法與合理的用法就好了敢辩。
False-y 比較
關(guān)于==
比較中 隱含 強(qiáng)制轉(zhuǎn)換的最常見(jiàn)的抱怨,來(lái)自于falsy值互相比較時(shí)它們?nèi)绾瘟钊顺泽@地動(dòng)作弟疆。
為了展示戚长,讓我們看一個(gè)關(guān)于falsy值比較的極端例子的列表,來(lái)瞧瞧哪一個(gè)是合理的怠苔,哪一個(gè)是麻煩的:
"0" == null; // false
"0" == undefined; // false
"0" == false; // true -- 噢同廉!
"0" == NaN; // false
"0" == 0; // true
"0" == ""; // false
false == null; // false
false == undefined; // false
false == NaN; // false
false == 0; // true -- 噢!
false == ""; // true -- 噢柑司!
false == []; // true -- 噢迫肖!
false == {}; // false
"" == null; // false
"" == undefined; // false
"" == NaN; // false
"" == 0; // true -- 噢!
"" == []; // true -- 噢攒驰!
"" == {}; // false
0 == null; // false
0 == undefined; // false
0 == NaN; // false
0 == []; // true -- 噢蟆湖!
0 == {}; // false
在這24個(gè)比較的類(lèi)表中,17個(gè)是十分合理和可預(yù)見(jiàn)的玻粪。比如隅津,我們知道""
和"NaN"
是根本不可能相等的值,并且它們確實(shí)不會(huì)強(qiáng)制轉(zhuǎn)換以成為寬松等價(jià)的劲室,而"0"
和0
是合理等價(jià)的饥瓷,而且確實(shí)強(qiáng)制轉(zhuǎn)換為寬松等價(jià)。
然而痹籍,這些比較中的7個(gè)被標(biāo)上了“噢!”晦鞋。作為誤判的成立蹲缠,它們更像是會(huì)將你陷進(jìn)去的坑。""
和0
絕對(duì)是有區(qū)別的不同的值悠垛,而且你很少會(huì)將它們作為等價(jià)的线定,所以它們的互相強(qiáng)制轉(zhuǎn)換是一種麻煩。注意這里沒(méi)有任何誤判的不成立确买。
瘋狂的情況
但是我們不必停留在此斤讥。我們可以繼續(xù)尋找更能引起麻煩的強(qiáng)制轉(zhuǎn)換:
[] == ![]; // true
噢,這看起來(lái)像是更高層次的瘋狂湾趾,對(duì)吧0派獭?你的大腦可能會(huì)欺騙你說(shuō)搀缠,你在將一個(gè)truthy和falsy值比較铛楣,所以結(jié)果true
是令人吃驚的,因?yàn)槲覀冎酪粋€(gè)值不可能同時(shí)為truthy和falsy艺普!
但這不是實(shí)際發(fā)生的事情簸州。讓我們把它分解一下鉴竭。我們了解!
一元操作符吧?它明確地使用ToBoolean
規(guī)則將操作數(shù)強(qiáng)制轉(zhuǎn)換為一個(gè)boolean
(而且它還會(huì)翻轉(zhuǎn)真假性)岸浑。所以在[] == ![]
執(zhí)行之前搏存,它實(shí)際上已經(jīng)被翻譯為了[] == false
。我們已將在上面的列表中見(jiàn)過(guò)了這種形式(false == []
)矢洲,所以它的令人吃驚的結(jié)果對(duì)我們來(lái)說(shuō)并不 新鮮璧眠。
其它的極端情況呢?
2 == [2]; // true
"" == [null]; // true
在關(guān)于ToNumber
的討論中我們說(shuō)過(guò)兵钮,右手邊的[2]
和[null]
值將會(huì)通過(guò)一個(gè)ToPrimitive
強(qiáng)制轉(zhuǎn)換蛆橡,以使我們可以方便地與左手邊的簡(jiǎn)單基本類(lèi)型值進(jìn)行比較。因?yàn)?code>array值的valueOf()
只是返回array
本身掘譬,強(qiáng)制轉(zhuǎn)換會(huì)退到array
的字符串化上泰演。
對(duì)于第一個(gè)比較的右手邊的值來(lái)說(shuō),[2]
將變?yōu)?code>"2"葱轩,然后它會(huì)ToNumber
強(qiáng)制轉(zhuǎn)換為2
睦焕。[null]
就直接變成""
。
那么靴拱,2 == 2
和"" == ""
是完全可以理解的垃喊。
如果你的直覺(jué)依然不喜歡這個(gè)結(jié)果,那么你的沮喪實(shí)際上與你可能認(rèn)為的強(qiáng)制轉(zhuǎn)換無(wú)關(guān)袜炕。這其實(shí)是在抱怨array
值在強(qiáng)制轉(zhuǎn)換為string
值時(shí)的默認(rèn)ToPrimitive
行為本谜。很可能,你只是希望[2].toString()
不返回"2"
偎窘,或者[null].toString()
不返回""
乌助。
但是這些string
強(qiáng)制轉(zhuǎn)換到底 應(yīng)該 得出什么結(jié)果?對(duì)于[2]
的string
強(qiáng)制轉(zhuǎn)換陌知,除了"2"
我確實(shí)想不出來(lái)其他合適的結(jié)果他托,也許是"[2]"
—— 但這可能會(huì)在其他的上下文中很奇怪!
你可以正確地制造另一個(gè)例子:因?yàn)?code>String(null)變成了"null"
仆葡,那么String([null])
也應(yīng)當(dāng)變成"null"
赏参。這是個(gè)合理的斷言。所以沿盅,它才是真正的犯人把篓。
隱含 強(qiáng)制轉(zhuǎn)換在這里并不邪惡。即使一個(gè)從[null]
到string
結(jié)果為""
的 明確 強(qiáng)制轉(zhuǎn)換也不腰涧。真正奇怪的是纸俭,array
值字符串化為它們內(nèi)容的等價(jià)物是否有道理,和它是如何發(fā)生的南窗。所以揍很,應(yīng)當(dāng)將你沮喪的原因指向String( [..] )
的規(guī)則郎楼,因?yàn)檫@里才是瘋狂起源的地方。也許根本就不應(yīng)該有array
的字符串化強(qiáng)制轉(zhuǎn)換?但這會(huì)在語(yǔ)言的其他部分造成許多的缺點(diǎn)。
另一個(gè)常被引用的著名的坑是:
0 == "\n"; // true
正如我們?cè)缦扔懻摰目?code>""陶夜,"\n"
(或" "
定页,或其他任何空格的組合)是通過(guò)ToNumber
強(qiáng)制轉(zhuǎn)換的担敌,而且結(jié)果為0
。你還希望空格被轉(zhuǎn)換為其他的什么number
值呢?明確的 Number()
給出0
會(huì)困擾你嗎?
空字符串和空格字符串可以轉(zhuǎn)換為的膘融,另一個(gè)真正唯一合理的number
值是NaN
。但這 真的 會(huì)更好嗎祭玉?" " == NaN
的比較當(dāng)然會(huì)失敗氧映,但是不清楚我們是否真的 修正 了任何底層的問(wèn)題。
真實(shí)世界中的JS程序由于0 == "\n"
而失敗的幾率非常之低脱货,而且這樣的極端用例很容比避免岛都。
在任何語(yǔ)言中,類(lèi)型轉(zhuǎn)換 總是 有極端用例 —— 強(qiáng)制轉(zhuǎn)換也不例外振峻。這里討論的是特定的一組極端用例的馬后炮臼疫,但不是針對(duì)強(qiáng)制轉(zhuǎn)換整體而言的爭(zhēng)論。
底線:你可能遇到的幾乎所有 普通值 間的瘋狂強(qiáng)制轉(zhuǎn)換(除了像早先那樣有意而為的valueOf()
或toString()
黑科技)扣孟,都能歸結(jié)為我們?cè)谏厦嬷赋龅?中情況的短列表烫堤。
對(duì)比這24個(gè)疑似強(qiáng)制轉(zhuǎn)換的坑,考慮另一個(gè)像這樣的列表:
42 == "43"; // false
"foo" == 42; // false
"true" == true; // false
42 == "42"; // true
"foo" == [ "foo" ]; // true
在這些非falsy凤价,非極端的用例中(而且我們簡(jiǎn)直可以向這個(gè)列表中添加無(wú)限多個(gè)比較)塔逃,強(qiáng)制轉(zhuǎn)換完全是安全,合理料仗,和可解釋的。
可行性檢查
好的伏蚊,當(dāng)我們深入觀察 隱含的 強(qiáng)制轉(zhuǎn)換時(shí)立轧,我確實(shí)找到了一些瘋狂的東西。難怪大多數(shù)開(kāi)發(fā)者聲稱強(qiáng)制轉(zhuǎn)換是邪惡而且應(yīng)該避開(kāi)的躏吊,對(duì)吧氛改?
但是讓我們退一步并做一下可行性檢查。
通過(guò)大量比較比伏,我們得到了一張7個(gè)麻煩的胜卤,坑人的強(qiáng)制轉(zhuǎn)換的列表,但我們還得到了另一張(至少17個(gè)赁项,但實(shí)際上有無(wú)限多個(gè))完全正常和可以解釋的強(qiáng)制轉(zhuǎn)換的列表葛躏。
如果你在尋找一本“把孩子和洗澡水一起潑出去”的教科書(shū)澈段,這就是了:由于一個(gè)僅有7個(gè)坑的列表,而拋棄整個(gè)強(qiáng)制轉(zhuǎn)換(安全且有效的行為的無(wú)限大列表)舰攒。
一個(gè)更謹(jǐn)慎的反應(yīng)是問(wèn)败富,“我如何使用強(qiáng)制轉(zhuǎn)換的 好的部分,而避開(kāi)這幾個(gè) 壞的部分 呢摩窃?”
讓我們?cè)倏匆淮芜@個(gè) 壞 列表:
"0" == false; // true -- 噢兽叮!
false == 0; // true -- 噢!
false == ""; // true -- 噢猾愿!
false == []; // true -- 噢鹦聪!
"" == 0; // true -- 噢!
"" == []; // true -- 噢蒂秘!
0 == []; // true -- 噢泽本!
這個(gè)列表中7個(gè)項(xiàng)目的4個(gè)與== false
比較有關(guān),我們?cè)缦日f(shuō)過(guò)你應(yīng)當(dāng) 總是材彪,總是 避免的观挎。
現(xiàn)在這個(gè)列表縮小到了3個(gè)項(xiàng)目。
"" == 0; // true -- 噢段化!
"" == []; // true -- 噢嘁捷!
0 == []; // true -- 噢!
這些是你在一般的JavaScript程序中使用的合理的強(qiáng)制轉(zhuǎn)換嗎显熏?在什么條件下它們會(huì)發(fā)生雄嚣?
我不認(rèn)為你在程序里有很大的可能要在一個(gè)boolean
測(cè)試中使用== []
,至少在你知道自己在做什么的情況下喘蟆。你可能會(huì)使用== ""
或== 0
缓升,比如:
function doSomething(a) {
if (a == "") {
// ..
}
}
如果你偶然調(diào)用了doSomething(0)
或doSomething([])
,你就會(huì)嚇一跳蕴轨。另一個(gè)例子:
function doSomething(a,b) {
if (a == b) {
// ..
}
}
再一次港谊,如果你調(diào)用doSomething("",0)
或doSomething([],"")
時(shí),它們會(huì)失敗橙弱。
所以歧寺,雖然這些強(qiáng)制轉(zhuǎn)換會(huì)咬到你的情況 可能 存在,而且你會(huì)小心地處理它們棘脐,但是它們可能不會(huì)在你的代碼庫(kù)中超級(jí)常見(jiàn)斜筐。
安全地使用隱含強(qiáng)制轉(zhuǎn)換
我能給你的最重要的建議是:檢查你的程序,并推理什么樣的值會(huì)出現(xiàn)在==
比較兩邊蛀缝。為了避免這樣的比較中的問(wèn)題顷链,這里有一些可以遵循的啟發(fā)性規(guī)則:
- 如果比較的任意一邊可能出現(xiàn)
true
或者false
值,那么就永遠(yuǎn)屈梁,永遠(yuǎn)不要使用==
嗤练。 - 如果比較的任意一邊可能出現(xiàn)
[]
榛了,""
,或0
這些值潭苞,那么認(rèn)真地考慮不使用==
忽冻。
在這些場(chǎng)景中,為了避免不希望的強(qiáng)制轉(zhuǎn)換此疹,幾乎可以確定使用===
要比使用==
好。遵循這兩個(gè)簡(jiǎn)單的規(guī)則蝗碎,可以有效地避免幾乎所有可能會(huì)傷害你的強(qiáng)制轉(zhuǎn)換的坑湖笨。
在這些情況下,使用更加明確/繁冗的方式會(huì)減少很多使你頭疼的東西蹦骑。
==
與===
的問(wèn)題其實(shí)可以更加恰當(dāng)?shù)乇硎鰹椋耗闶欠駪?yīng)當(dāng)在比較中允許強(qiáng)制轉(zhuǎn)換慈省?
在許多情況下這樣的強(qiáng)制轉(zhuǎn)換會(huì)很有用,允許你更簡(jiǎn)練地表述一些比較邏輯(例如眠菇,null
和undefined
)边败。
對(duì)于整體來(lái)說(shuō),相對(duì)有幾個(gè) 隱含 強(qiáng)制轉(zhuǎn)換會(huì)真的很危險(xiǎn)的情況捎废。但是在這些地方笑窜,為了安全起見(jiàn),絕對(duì)要使用===
登疗。
提示: 另一個(gè)強(qiáng)制轉(zhuǎn)換保證 不會(huì) 咬到你的地方是typeof
操作符排截。typeof
總是將返回給你7中字符串之一(見(jiàn)第一章),它們中沒(méi)有一個(gè)是空""
字符串辐益。這樣断傲,檢查某個(gè)值的類(lèi)型時(shí)不會(huì)有任何情況與 隱含 強(qiáng)制轉(zhuǎn)換相沖突。typeof x == "function"
就像typeof x === "function"
一樣100%安全可靠智政。從字面意義上將认罩,語(yǔ)言規(guī)范說(shuō)這種情況下它們的算法是相同的。所以续捂,不要只是因?yàn)槟愕拇a工具告訴你這么做垦垂,或者(最差勁兒的)在某本書(shū)中有人告訴你 不要考慮它,而盲目地到處使用===
疾忍。你掌管著你的代碼的質(zhì)量。
隱含 強(qiáng)制轉(zhuǎn)換是邪惡和危險(xiǎn)的嗎床三?在幾個(gè)情況下一罩,是的,但總體說(shuō)來(lái)撇簿,不是聂渊。
做一個(gè)負(fù)責(zé)任和成熟的開(kāi)發(fā)者差购。學(xué)習(xí)如何有效并安全地使用強(qiáng)制轉(zhuǎn)換(明確的 和 隱含的 兩者)的力量。并教你周?chē)娜艘策@么做汉嗽。
這里是由Alex Dorey (@dorey on GitHub)制作的一個(gè)方便的表格欲逃,將各種比較進(jìn)行了可視化:
出處:https://github.com/dorey/JavaScript-Equality-Table
抽象關(guān)系比較
雖然這部分的 隱含 強(qiáng)制轉(zhuǎn)換經(jīng)常不為人所注意,但無(wú)論如何考慮比較a < b
時(shí)發(fā)生了什么是很重要的(和我們?nèi)绾紊钊霗z視a == b
類(lèi)似)饼暑。
在ES5語(yǔ)言規(guī)范的11.8.5部分的“抽象關(guān)系型比較”算法稳析,實(shí)質(zhì)上把自己分成了兩個(gè)部分:如果比較涉及兩個(gè)string
值要做什么(后半部分),和除此之外的其他值要做什么(前半部分)弓叛。
注意: 這個(gè)算法僅僅定義了a < b
彰居。所以,a > b
作為b < a
處理撰筷。
這個(gè)算法首先在兩個(gè)值上調(diào)用ToPrimitive
強(qiáng)制轉(zhuǎn)換陈惰,如果兩個(gè)調(diào)用的返回值之一不是string
,那么就使用ToNumber
操作規(guī)則將這兩個(gè)值強(qiáng)制轉(zhuǎn)換為number
值毕籽,并進(jìn)行數(shù)字的比較抬闯。
舉例來(lái)說(shuō):
var a = [ 42 ];
var b = [ "43" ];
a < b; // true
b < a; // false
注意: 早先討論的關(guān)于-0
和NaN
在==
算法中的類(lèi)似注意事項(xiàng)也適用于這里。
然而关筒,如果<
比較的兩個(gè)值都是string
的話溶握,就會(huì)在字符上進(jìn)行簡(jiǎn)單的字典順序(自然的字母順序)比較:
var a = [ "42" ];
var b = [ "043" ];
a < b; // false
a
和b
不會(huì) 被強(qiáng)制轉(zhuǎn)換為number
,因?yàn)樗鼈儠?huì)在兩個(gè)array
的ToPrimitive
強(qiáng)制轉(zhuǎn)換后成為string
平委。所以奈虾,"42"
將會(huì)與"043"
一個(gè)字符一個(gè)字符地進(jìn)行比較,從第一個(gè)字符開(kāi)始廉赔,分別是"4"
和"0"
肉微。因?yàn)?code>"0"在字典順序上 小于 "4"
,所以這個(gè)比較返回false
蜡塌。
完全相同的行為和推理也適用于:
var a = [ 4, 2 ];
var b = [ 0, 4, 3 ];
a < b; // false
這里碉纳,a
變成了"4,2"
而b
變成了"0,4,3"
,而字典順序比較和前一個(gè)代碼段一模一樣馏艾。
那么這個(gè)怎么樣:
var a = { b: 42 };
var b = { b: 43 };
a < b; // ??
a < b
也是false
劳曹,因?yàn)?code>a變成了[object Object]
而b
變成了[object Object]
,所以明顯地a
在字典順序上不小于b
琅摩。
但奇怪的是:
var a = { b: 42 };
var b = { b: 43 };
a < b; // false
a == b; // false
a > b; // false
a <= b; // true
a >= b; // true
為什么a == b
不是true
铁孵?它們是相同的string
值("[object Object]"
),所以看起來(lái)它們應(yīng)當(dāng)相等房资,對(duì)吧蜕劝?不。回憶一下前面關(guān)于==
如何與object
引用進(jìn)行工作的討論岖沛。
那么為什么a <= b
和a >= b
的結(jié)果為true
暑始,如果a < b
和a == b
和a > b
都是false
?
因?yàn)檎Z(yǔ)言規(guī)范說(shuō)婴削,對(duì)于a <= b
廊镜,它實(shí)際上首先對(duì)b < a
求值,然后反轉(zhuǎn)那個(gè)結(jié)果唉俗。因?yàn)?code>b < a也是false
嗤朴,所以a <= b
的結(jié)果為true
。
到目前為止你解釋<=
在做什么的方式可能是:“小于 或 等于”互躬。而這可能完全相反播赁,JS更準(zhǔn)確地將<=
考慮為“不大于”(!(a > b)
,JS將它作為(!b < a)
)吼渡。另外容为,a >= b
被解釋為它首先被考慮為b <= a
,然后實(shí)施相同的推理寺酪。
不幸的是坎背,沒(méi)有像等價(jià)那樣的“嚴(yán)格的關(guān)系型比較”。換句話說(shuō)寄雀,沒(méi)有辦法防止a < b
這樣的關(guān)系型比較發(fā)生 隱含的 強(qiáng)制轉(zhuǎn)換得滤,除非在進(jìn)行比較之前就明確地確保a
和b
是同種類(lèi)型。
使用與我們?cè)缦?code>==與===
合理性檢查的討論相同的推理方法盒犹。如果強(qiáng)制轉(zhuǎn)換有幫助并且合理安全懂更,比如比較42 < "43"
,就使用它急膀。另一方面沮协,如果你需要在關(guān)系型比較上獲得安全性,那么在使用<
(或>
)之前卓嫂,就首先 明確地強(qiáng)制轉(zhuǎn)換 這些值慷暂。
var a = [ 42 ];
var b = "043";
a < b; // false -- 字符串比較!
Number( a ) < Number( b ); // true -- 數(shù)字比較晨雳!
復(fù)習(xí)
在這一章中行瑞,我們將注意力轉(zhuǎn)向了JavaScript類(lèi)型轉(zhuǎn)換如何發(fā)生,也叫 強(qiáng)制轉(zhuǎn)換餐禁,按性質(zhì)來(lái)說(shuō)它要么是 明確的 要么是 隱含的血久。
強(qiáng)制轉(zhuǎn)換的名聲很壞,但它實(shí)際上在許多情況下很有幫助帮非。對(duì)于負(fù)責(zé)任的JS開(kāi)發(fā)者來(lái)說(shuō)氧吐,一個(gè)重要的任務(wù)就是花時(shí)間去學(xué)習(xí)強(qiáng)制轉(zhuǎn)換的里里外外绷旗,來(lái)決定哪一部分將幫助他們改進(jìn)代碼,哪一部分他們真的應(yīng)該回避副砍。
明確的 強(qiáng)制轉(zhuǎn)換時(shí)這樣一種代碼,它很明顯地有意將一個(gè)值從一種類(lèi)型轉(zhuǎn)換到另一種類(lèi)型庄岖。它的益處是通過(guò)減少困惑來(lái)增強(qiáng)了代碼的可讀性和可維護(hù)性豁翎。
隱含的 強(qiáng)制轉(zhuǎn)換是作為一些其他操作的“隱藏的”副作用而存在的,將要發(fā)生的類(lèi)型轉(zhuǎn)換并不明顯隅忿。雖然看起來(lái) 隱含的 強(qiáng)制轉(zhuǎn)換是 明確的 反面心剥,而且因此是不好的(確實(shí),很多人這么認(rèn)為1惩)优烧,但是實(shí)際上 隱含的 強(qiáng)制轉(zhuǎn)換也是為了增強(qiáng)代碼的可讀性。
特別是對(duì)于 隱含的链峭,強(qiáng)制轉(zhuǎn)換必須被負(fù)責(zé)地畦娄,有意識(shí)地使用。懂得為什么你在寫(xiě)你正在寫(xiě)的代碼弊仪,和它是如何工作的熙卡。同時(shí)也要努力編寫(xiě)其他人容易學(xué)習(xí)和理解的代碼。