第四章:強(qiáng)制轉(zhuǎn)換2

特別說(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

在本章的早先,我們探索了stringnumber值之間的 明確 強(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 + 33 + 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 * 1a / 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í)施麻献。

那么,stringnumber值之間的 隱含 強(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(很明顯帆疟,01)的強(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)換為truefalse倒堕。這樣你就可以像onlyOne( "42", 0 )這樣傳入非boolean值了灾测,而且它依然可以如意料的那樣工作(要不然,你將會(huì)得到string連接垦巴,而且邏輯也不正確)媳搪。

一旦我們確認(rèn)它是一個(gè)boolean铭段,我們就使用Number(..)進(jìn)行另一個(gè) 明確的 強(qiáng)制轉(zhuǎn)換來(lái)確保值是01

這個(gè)工具的 明確 強(qiáng)制轉(zhuǎn)換形式“更好”嗎秦爆?它確實(shí)像代碼注釋中解釋的那樣避開(kāi)了NaN的陷阱稠项。但是,這最終要看你的需要鲜结。我個(gè)人認(rèn)為前一個(gè)版本展运,依賴于 隱含的 強(qiáng)制轉(zhuǎn)換更優(yōu)雅(如果你不傳入undefinedNaN),而 明確的 版本是一種不必要的繁冗精刷。

但與我們?cè)谶@里討論的幾乎所有東西一樣拗胜,這是一個(gè)主觀判斷。

注意: 不管是 隱含的 還是 明確的 方式怒允,你可以通過(guò)將最后的比較從1改為25埂软,來(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)換呢的止?

  1. 在一個(gè)if (..)語(yǔ)句中的測(cè)試表達(dá)式。
  2. 在一個(gè)for ( .. ; .. ; .. )頭部的測(cè)試表達(dá)式(第二個(gè)子句)着撩。
  3. while (..)do..while(..)循環(huán)中的測(cè)試表達(dá)式诅福。
  4. ? :三元表達(dá)式中的測(cè)試表達(dá)式(第一個(gè)子句)。
  5. ||(“邏輯或”)和&&(“邏輯與”)操作符左手邊的操作數(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é)果為truefalse援雇,而在JS中(就此而言還有Python和Ruby!)椎扬,結(jié)果來(lái)自于值本身惫搏。

||&&操作符都在 第一個(gè)操作數(shù)ac) 上進(jìn)行boolean測(cè)試。如果這個(gè)操作數(shù)還不是boolean(就像在這里一樣)蚕涤,就會(huì)發(fā)生一次普通的ToBoolean強(qiáng)制轉(zhuǎn)換筐赔,這樣測(cè)試就可以進(jìn)行了。

對(duì)于||操作符揖铜,如果測(cè)試結(jié)果為true茴丰,||表達(dá)式就將 第一個(gè)操作數(shù) 的值(ac)作為結(jié)果。如果測(cè)試結(jié)果為false||表達(dá)式就將 第二個(gè)操作數(shù) 的值(b)作為結(jié)果贿肩。

相反地峦椰,對(duì)于&&操作符,如果測(cè)試結(jié)果為true汰规,&&表達(dá)式將 第二個(gè)操作數(shù) 的值(b)作為結(jié)果汤功。如果測(cè)試結(jié)果為false,那么&&表達(dá)式就將 第一個(gè)操作數(shù) 的值(ac)作為結(jié)果溜哮。

||&&表達(dá)式的結(jié)果總是兩個(gè)操作數(shù)之一的底層值滔金,不是(可能是被強(qiáng)制轉(zhuǎn)換來(lái)的)測(cè)試的結(jié)果。在c && b中茂嗓,cnull餐茵,因此是falsy。但是&&表達(dá)式本身的結(jié)果為nullc中的值)在抛,不是用于測(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 && ba ? 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ì)得出truefalse的事實(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(包括functionarray)的==寬松相等性比較濒翻。這樣的兩個(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à)操作也是一樣的旦棉。

比較:stringnumber

為了展示==強(qiáng)制轉(zhuǎn)換齿风,首先讓我們建立本章中早先的stringnumber的例子:

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ō):

  1. 如果Type(x)是Number而Type(y)是String嫁盲,
    返回比較x == ToNumber(y)的結(jié)果。
  2. 如果Type(x)是String而Type(y)是Number薪寓,
    返回比較ToNumber(x) == y的結(jié)果亡资。

警告: 語(yǔ)言規(guī)范中使用NumberString作為類(lèi)型的正式名稱,雖然這本書(shū)中偏好使用numberstring指代基本類(lèi)型向叉。別讓語(yǔ)言規(guī)范中首字母大寫(xiě)的NumberNumber()原生函數(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è)值直接與truefalse相比較時(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

  1. 如果Type(x)是Boolean搬泥,
    返回比較 ToNumber(x) == y 的結(jié)果桑寨。
  2. 如果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è)booleantrue),而是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)心這種真/假的思維陷阱哲嘲。

比較:nullundefined

另一個(gè) 隱含 強(qiáng)制轉(zhuǎn)換的例子可以在nullundefined值之間的==寬松等價(jià)中看到。又再一次引述ES5語(yǔ)言規(guī)范媳禁,條款11.9.3.2-3:

  1. 如果x是null而y是undefined眠副,返回true。
  2. 如果x是undefined而y是null损话,返回true侦啸。

當(dāng)使用==寬松等價(jià)比較nullundefined,它們是互相等價(jià)(也就是互相強(qiáng)制轉(zhuǎn)換)的丧枪,而且在整個(gè)語(yǔ)言中不會(huì)等價(jià)于其他值了光涂。

這意味著nullundefined對(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

nullundefined之間的強(qiáng)制轉(zhuǎn)換是安全且可預(yù)見(jiàn)的,而且在這樣的檢查中沒(méi)有其他的值會(huì)給出測(cè)試成立的誤判恋博。我推薦使用這種強(qiáng)制轉(zhuǎn)換來(lái)允許nullundefined是不可區(qū)分的齐佳,如此將它們作為相同的值對(duì)待。

比如:

var a = doSomething();

if (a == null) {
    // ..
}

a == null檢查僅在doSomething()返回null或者undefined時(shí)才會(huì)通過(guò)债沮,而在任何其他值的情況下將會(huì)失敗炼吴,即便是0false疫衩,和""這樣的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)量(stringnumber假褪,或boolean)進(jìn)行比較署咽,ES5語(yǔ)言規(guī)范在條款11.9.3.8-9中這樣說(shuō)道:

  1. 如果Type(x)是一個(gè)String或者Number而Type(y)是一個(gè)Object,
    返回比較 x == ToPrimitive(y) 的結(jié)果生音。
  2. 如果Type(x)是一個(gè)Object而Type(y)是String或者Number宁否,
    返回比較 ToPrimitive(x) == y 的結(jié)果。

注意: 你可能注意到了缀遍,這些條款僅提到了StringNumber家淤,而沒(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殴瘦,所以ab被認(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 == btrue是因?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

nullundefined不能被裝箱 —— 它們沒(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è)陷阱中改执,這是由于23都不會(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í) 等于23。但是“同時(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ī)則:

  1. 如果比較的任意一邊可能出現(xiàn)true或者false值,那么就永遠(yuǎn)屈梁,永遠(yuǎn)不要使用==嗤练。
  2. 如果比較的任意一邊可能出現(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)練地表述一些比較邏輯(例如眠菇,nullundefined)边败。

對(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)于-0NaN==算法中的類(lèi)似注意事項(xiàng)也適用于這里。

然而关筒,如果<比較的兩個(gè)值都是string的話溶握,就會(huì)在字符上進(jìn)行簡(jiǎn)單的字典順序(自然的字母順序)比較:

var a = [ "42" ];
var b = [ "043" ];

a < b;  // false

ab 不會(huì) 被強(qiáng)制轉(zhuǎn)換為number,因?yàn)樗鼈儠?huì)在兩個(gè)arrayToPrimitive強(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 <= ba >= b的結(jié)果為true暑始,如果a < ba == ba > 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)行比較之前就明確地確保ab是同種類(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í)和理解的代碼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末励饵,一起剝皮案震驚了整個(gè)濱河市驳癌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌役听,老刑警劉巖颓鲜,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異典予,居然都是意外死亡甜滨,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)熙参,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)艳吠,“玉大人,你說(shuō)我怎么就攤上這事孽椰≌衙洌” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵黍匾,是天一觀的道長(zhǎng)栏渺。 經(jīng)常有香客問(wèn)我,道長(zhǎng)锐涯,這世上最難降的妖魔是什么磕诊? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上霎终,老公的妹妹穿的比我還像新娘滞磺。我一直安慰自己,他們只是感情好莱褒,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布击困。 她就那樣靜靜地躺著,像睡著了一般广凸。 火紅的嫁衣襯著肌膚如雪阅茶。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,046評(píng)論 1 285
  • 那天谅海,我揣著相機(jī)與錄音脸哀,去河邊找鬼。 笑死扭吁,一個(gè)胖子當(dāng)著我的面吹牛撞蜂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播侥袜,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼谅摄,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了系馆?” 一聲冷哼從身側(cè)響起送漠,我...
    開(kāi)封第一講書(shū)人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎由蘑,沒(méi)想到半個(gè)月后闽寡,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡尼酿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年爷狈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片裳擎。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡涎永,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鹿响,到底是詐尸還是另有隱情羡微,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布惶我,位于F島的核電站妈倔,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏绸贡。R本人自食惡果不足惜盯蝴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一毅哗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧捧挺,春花似錦虑绵、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至鸣峭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間酥艳,已是汗流浹背摊溶。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留充石,地道東北人莫换。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像骤铃,于是被迫代替她去往敵國(guó)和親拉岁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容