值類型轉(zhuǎn)換
將值從一種類型轉(zhuǎn)換為另一種類型通常稱為類型轉(zhuǎn)換夺巩,這是顯示的情況板熊;隱式的情況稱為強制類型轉(zhuǎn)換蛋济。JavaScript中的強制類型轉(zhuǎn)換總是返回標(biāo)量基本類型值西设,如字符串瓣铣、數(shù)字和布爾值,不會返回對象和函數(shù)贷揽。我們介紹過“封裝”棠笑,就是為標(biāo)量基本類型值封裝一個相應(yīng)類型的對象,但這并非嚴(yán)格意義上的強制類型轉(zhuǎn)換禽绪。
也可以這樣來區(qū)分:類型轉(zhuǎn)換發(fā)生在靜態(tài)類型語言的編譯階段蓖救,而強制類型轉(zhuǎn)換則發(fā)生在動態(tài)類型語言的運行時。
var a = 42;
var b = a + ""; // 隱式強制類型轉(zhuǎn)換
var c = String( a ); // 顯式強制類型轉(zhuǎn)換
對變量b而言丐一,強制類型轉(zhuǎn)換是隱式的藻糖;由于+運算符的其中一個操作數(shù)是字符串,所以是字符串拼接操作库车,結(jié)果是數(shù)字42被強制類型轉(zhuǎn)換為相應(yīng)的字符串“42”。
ToString
它負(fù)責(zé)處理非字符串到字符串的強制類型轉(zhuǎn)換樱拴∧埽基本類型值的字符串化規(guī)則為:null轉(zhuǎn)換為“null”洋满,undefined轉(zhuǎn)換為“undefined”,true轉(zhuǎn)換為“true”珍坊。數(shù)字的字符串化則遵循通用規(guī)則牺勾。不過那些極小和極大的數(shù)字使用指數(shù)形式:
// 1.07 連續(xù)乘以七個 1000
var a = 1.07 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000;
// 七個1000一共21位數(shù)字
a.toString(); // "1.07e21"
對普通對象來說,除非自行定義阵漏,否則toString()(Object.prototype.toString())返回內(nèi)部屬性[[Class]]的值驻民,如“[object Object]”。
數(shù)組的默認(rèn)toString()方法經(jīng)過了重新定義履怯,將所有單元字符串化以后再用“回还,”連接起來:
var a = [1,2,3];
a.toString(); // "1,2,3"
JSON字符串化
工具函數(shù)JSON.stringify(..)在將JSON對象序列化為字符串時也用到了ToString。
對于大多數(shù)簡單值來說叹洲,JSON字符串和toString()的效果基本相同柠硕,只不過序列化的結(jié)果總是字符串:
JSON.stringify( 42 ); // "42"
JSON.stringify( "42" ); // ""42"" (含有雙引號的字符串)
JSON.stringify( null ); // "null"
JSON.stringify( true ); // "true"
所有安全的JSON值都可以使用JSON.stringify(..)字符串化。安全的JSON值是指你能夠呈現(xiàn)為有效JSON格式的值运提。
JSON.stringify(..)在對象中遇到undefined蝗柔、function和symbol時會自動將其忽略,在數(shù)組中則會返回null(以保證單元位置不變)民泵。
JSON.stringify(undefined); // undefined
JSON.stringify(function () { }); // undefined
JSON.stringify(
[1, undefined, function () { }, 4]
); // "[1,null,null,4]"
JSON.stringify(
{ a: 2, b: function () { } }
); // "{"a":2}"
對包含循環(huán)引用的對象執(zhí)行JSON.stringify(..)會出錯癣丧。
我們可以向JSON.stringify(..)傳遞一個可選參數(shù)replacer,它可以是數(shù)組或者函數(shù)栈妆,用來指定對象序列化過程中哪些屬性應(yīng)該被處理坎缭,哪些應(yīng)該被排除。
如果replacer是一個數(shù)組签钩,那么它必須是一個字符串?dāng)?shù)組掏呼,其中包含序列化要處理的對象的屬性名稱,除此之外其他的屬性則被忽略铅檩。
如果replacer是一個函數(shù)憎夷,它會對對象本身調(diào)用一次,然后對對象中的每個屬性各調(diào)用一次昧旨,每次傳遞兩個參數(shù)拾给,鍵和值。如果要忽略某個鍵就返回undefined兔沃,否則返回指定的值蒋得。
var a = {
b: 42,
c: "42",
d: [1, 2, 3]
};
JSON.stringify(a, ["b", "c"]); // "{"b":42,"c":"42"}"
JSON.stringify(a, function (k, v) {
if (k !== "c") return v;
});
// "{"b":42,"d":[1,2,3]}"
JSON.string還有一個可選參數(shù)space,用來指定輸出的縮進(jìn)格式乒疏。space為正整數(shù)時是指定每一級縮進(jìn)的字符數(shù)额衙,它還可以是字符串,此時最前面的十個字符被用于每一級的縮進(jìn):
var a = {
b: 42,
c: "42",
d: [1, 2, 3]
};
JSON.stringify(a, null, 3);
// "{
// "b": 42,
// "c": "42",
// "d": [
// 1,
// 2,
// 3
// ]
// }"
JSON.stringify( a, null, "-----" );
// "{
// -----"b": 42,
// -----"c": "42",
// -----"d": [
// ----------1,
// ----------2,
// ----------3
// -----]
// }"
請記住,JSON.stringify(..)并不是強制類型轉(zhuǎn)換窍侧。在這里介紹是因為它涉及ToString強制類型轉(zhuǎn)換县踢,具體表現(xiàn)在以下兩點:
(1)字符串、數(shù)字伟件、布爾值和null的JSON.stringify(..)規(guī)則與ToString基本相同硼啤。
(2)如果傳遞給JSON.stringify(..)的對象中定義了toJSON()方法,那么該方法會在字符串化前調(diào)用斧账,以便將對象轉(zhuǎn)化為安全的JSON值谴返。
ToNumber
有時候我們需要將非數(shù)字值當(dāng)做數(shù)字來使用,比如數(shù)學(xué)運算咧织。為此ES5規(guī)范定義了抽象操作ToNumber嗓袱。
其中true轉(zhuǎn)換為1,false轉(zhuǎn)換為0拯爽。undefined轉(zhuǎn)換為NaN索抓,null轉(zhuǎn)換為0。
ToNumber對字符串的處理基本遵循數(shù)字常量的相關(guān)規(guī)則/語法毯炮。處理失敗時返回NaN(處理數(shù)字常量是把你時會產(chǎn)生語法錯誤)逼肯。不同之處是ToNumber對以0開頭的十六進(jìn)制數(shù)并不按十六進(jìn)制處理(而是按十進(jìn)制)。
對象(包括數(shù)組)會首先被轉(zhuǎn)換為相應(yīng)的基本類型值桃煎,如果返回的是非數(shù)字的基本類型值篮幢,則再遵循以上規(guī)則將其強制轉(zhuǎn)換為數(shù)字。
為了將值轉(zhuǎn)換為相應(yīng)的基本類型值为迈,抽象操作ToPromitive會首先(通過內(nèi)部操作DefaultValue)檢查該值是否有valueOf()方法三椿。如果有并且返回基本類型值,就使用該值進(jìn)行強制類型轉(zhuǎn)換葫辐。如果沒有就使用toString()的返回值(如果存在)來進(jìn)行強制類型轉(zhuǎn)換搜锰。
如果valueOf()和toString()均不返回基本類型值,會產(chǎn)生TypeError錯誤耿战。
從ES5開始蛋叼,使用Object.create(null)創(chuàng)建的對象[[Prototype]]屬性為null,并且沒有valueIOf()和toString()方法剂陡,因此無法進(jìn)行強制類型轉(zhuǎn)換狈涮。
ToBoolean
JavaScript中有兩個關(guān)鍵詞true和false,分別代表布爾類型中的真和假鸭栖。我們常誤以為數(shù)值1和0分別等同于true和false歌馍。在有些語言中可能是這樣,但在JavaScript中布爾值和數(shù)字是不一樣的晕鹊。雖然我們可以將1強制類型轉(zhuǎn)換為true松却,將0強制類型轉(zhuǎn)換為false暴浦,反之亦然,但它們并不是一回事玻褪。
假值
JavaScript中的值可以分為以下兩類:
(1)可以被強制類型轉(zhuǎn)換為false的值
(2)其他(被強制類型轉(zhuǎn)換為true的值)
以下這些是假值:
- undefined
- null
- false
- +0肉渴、-0和NaN
- “”
假值的布爾強制類型轉(zhuǎn)換結(jié)果為false公荧。
從邏輯上說带射,假值列表以外的都應(yīng)該是真值。但JavaScript規(guī)范對此并沒有明確定義循狰,只是給出了一些示例窟社,例如規(guī)定所有的對象都是真值,我們可以理解為假值列表意外的值都是真值绪钥。
2灿里、假值對象
這個標(biāo)題似乎有點自相矛盾。前面講過規(guī)范規(guī)定所有的對象都是真值程腹,怎么還會有假值對象呢匣吊?
瀏覽器在某些情況下,在常規(guī)JavaScript語法基礎(chǔ)上自己創(chuàng)建了一些外表值寸潦,這些就是“假值對象”色鸳。
假值對象看起來和普通對象并無二致(都有屬性,等等)见转,但將它們強制類型轉(zhuǎn)換為布爾值時結(jié)果為false命雀。
最常見的例子是document.all,它是一個類數(shù)組對象斩箫,包含了頁面上的所有元素吏砂,由DOM(而不是JavaScript引擎)提供給JavaScript程序使用。它以前曾是一個真正意義上的對象乘客,布爾強制類型轉(zhuǎn)換結(jié)果為true狐血,不過現(xiàn)在它是一個假值對象。
document.all并不是一個標(biāo)準(zhǔn)用法易核,早就被廢止了匈织。
那為什么它要是假值呢?因為我們經(jīng)常通過將document.all強制類型轉(zhuǎn)換為布爾值(比如if語句中)來判斷瀏覽器是否是老版本的IE耸成。IE自誕生之日起就始終遵循瀏覽器標(biāo)準(zhǔn)报亩,較其他瀏覽器更為有力地推動了Web的發(fā)展。
if(document.all){/it's IE/}依然存在于許多程序中井氢,也許會一直存在下去弦追,這對IE的用戶體驗來說不是一件好事。
3花竞、真值
真值就是假值列表之外的值劲件。
var a = "false";
var b = "0";
var c = "''";
var d = Boolean( a && b && c );
d;//true
var a = []; // 空數(shù)組——是真值還是假值掸哑?
var b = {}; // 空對象——是真值還是假值?
var c = function(){}; // 空函數(shù)——是真值還是假值零远?
var d = Boolean( a && b && c );
d;
d依然是true苗分。還是同樣的道理,[],{}和function(){}都不在假值列表中牵辣,因此它們都是真值摔癣。
字符串和數(shù)字之間的顯式轉(zhuǎn)換
var a = 42;
var b = String( a );
var c = "3.14";
var d = Number( c );
b; // "42"
d; // 3.14
除了String(..)和Number(..)以外,還有其他方法可以實現(xiàn)字符串和數(shù)字之間的顯式轉(zhuǎn)換:
var a = 42;
var b = a.toString();
var c = "3.14";
var d = +c;
b; // "42"
d; // 3.14
1纬向、日期顯式轉(zhuǎn)換為數(shù)字
一元運算符+的另一個常見用途是將日期(Date)對象強制類型轉(zhuǎn)換為數(shù)字择浊,返回結(jié)果為Unix時間戳i,以微妙為單位:
var d = new Date( "Mon, 18 Aug 2014 08:53:06 CDT" );
+d; // 1408369986000
我們常用下面的方法來獲得當(dāng)前的時間戳逾条,例如:
var timestamp = +new Date();
JavaScript有有一處奇特的語法琢岩,即構(gòu)造函數(shù)沒有參數(shù)時可以不用帶()。于是我們可能會碰到
var timestamp = +new Date;
這樣的寫法师脂。這樣能否提高代碼可讀性還存在爭議担孔,因為這僅用于new fn(),對一般的函數(shù)用fn()并不適用吃警。
2糕篇、奇特的~運算符
0 | -0; // 0
0 | NaN; // 0
0 | Infinity; // 0
0 | -Infinity; // 0
indexOf(..)不僅能夠得到子字符串的位置,還可以用來檢查字符串中是否包含指定的子字符串汤徽,相當(dāng)于一個條件判斷娩缰。例如:
var a = "Hello World";
if (a.indexOf("lo") >= 0) { // true
// 找到匹配!
}
if (a.indexOf("lo") != -1) { // true
// 找到匹配谒府!
}
if (a.indexOf("ol") < 0) { // true
// 沒有找到匹配拼坎!
}
if (a.indexOf("ol") == -1) { // true
// 沒有找到匹配!
}
= 0 和== -1這樣的寫法不是很好完疫,稱為“抽象滲漏”泰鸡,意思是在代碼中暴露了底層的實現(xiàn)細(xì)節(jié),這里是指用-1作為失敗時的返回值壳鹤,這些細(xì)節(jié)應(yīng)該被屏蔽掉盛龄。
現(xiàn)在我們終于明白有什么用處了!和indexOf()一起可以將結(jié)果強制類型轉(zhuǎn)換(實際上僅僅是轉(zhuǎn)換)為真/假值:
var a = "Hello World";
~a.indexOf("lo"); // -4 <-- 真值!
if (~a.indexOf("lo")) { // true
// 找到匹配芳誓!
}
~a.indexOf("ol"); // 0 <-- 假值!
!~a.indexOf("ol"); // true
if (!~a.indexOf("ol")) { // true
// 沒有找到匹配余舶!
}
如果indexOf(..)返回-1,~將其轉(zhuǎn)換為假值0锹淌,其他情況一律轉(zhuǎn)換為真值匿值。
3、字位截除
一些開發(fā)人員使用~~來截除數(shù)字值的小樹部分赂摆,以為這和Math.floor(..)的效果一樣挟憔,實際上并非如此钟些。
Math.floor( -49.6 ); // -50
~~-49.6; // -49
顯式解析數(shù)字字符串
解析字符串中的數(shù)字和將字符串強制類型轉(zhuǎn)換為數(shù)字的返回結(jié)果都是數(shù)字。但解析和轉(zhuǎn)換兩者之間還是有明顯的差別绊谭。
var a = "42";
var b = "42px";
Number( a ); // 42
parseInt( a ); // 42
Number( b ); // NaN
parseInt( b ); // 42
解析允許字符串中含有非數(shù)字字符政恍,解析按從做到右的順序,如果遇到非數(shù)字字符就停止达传。而轉(zhuǎn)換不允許出現(xiàn)非數(shù)字字符篙耗,否則會失敗并返回NaN。
ES5自己的parseInt(..)有一個坑導(dǎo)致了很多bug趟大。即如果沒有第二個參數(shù)來指定轉(zhuǎn)換的基數(shù)鹤树,parseInt(..)會根據(jù)字符串的第一個字符來自行決定基數(shù)铣焊。
如果第一個字符是x或x逊朽,則轉(zhuǎn)換為十六進(jìn)制數(shù)字。如果是0曲伊,則轉(zhuǎn)換為八進(jìn)制數(shù)字叽讳。
以x和x開頭的十六進(jìn)制相對來說還不太容易搞錯,而八進(jìn)制則不然坟募。例如:
var hour = parseInt(selectedHour.value);
var minute = parseInt(selectedMinute.value);
console.log(
"The time you selected was: " + hour + ":" + minute
);
上面的代碼看似沒有問題岛蚤,但是當(dāng)小時為08、分鐘為09時懈糯,結(jié)果是0:0涤妒,因為8和9都不是有效的八進(jìn)制數(shù)。
將第二個參數(shù)設(shè)置為10赚哗,即可避免這個問題:
var hour = parseInt( selectedHour.value, 10 );
var minute = parseInt( selectedMiniute.value, 10 );
從ES5開始parseInt(..)默認(rèn)轉(zhuǎn)換為十進(jìn)制數(shù)她紫,除非另外指定。如果你的代碼需要在ES5之前的環(huán)境運行屿储,請記得將第二個參數(shù)設(shè)置為10贿讹。
解析非字符串
曾經(jīng)有人發(fā)帖吐槽過parseInt(..)的一個坑:
parseInt( 1/0, 19 ); // 18
parseInt(1/0,19)實際上是parseInt("Infinity",19)。第一個字符是“I”够掠,以19為基數(shù)時值為18民褂。第二個字符“n”不是一個有效的數(shù)字字符,解析到此為止疯潭,和“42px”中的“p”一樣赊堪。
此外還有一些看起來奇怪但實際上解釋得通的例子:
parseInt( 0.000008 ); // 0 ("0" 來自于 "0.000008")
parseInt( 0.0000008 ); // 8 ("8" 來自于 "8e-7")
parseInt( false, 16 ); // 250 ("fa" 來自于 "false")
parseInt( parseInt, 16 ); // 15 ("f" 來自于 "function..")
parseInt( "0x10" ); // 16
parseInt( "103", 2 ); // 2
顯式轉(zhuǎn)換為布爾值
與前面的String(..)和Number(..)一樣,Boolean(..)(不帶new)是顯式的ToBoolean強制類型轉(zhuǎn)換:
var a = "0";
var b = [];
var c = {};
var d = "";
var e = 0;
var f = null;
var g;
Boolean( a ); // true
Boolean( b ); // true
Boolean( c ); // true
Boolean( d ); // false
Boolean( e ); // false
Boolean( f ); // false
Boolean( g ); // false
和前面講過的+類似竖哩,一元運算符哭廉!顯式地將值強制類型轉(zhuǎn)換為布爾值。但是它同時還將真值反轉(zhuǎn)為假值(或者將假值反轉(zhuǎn)為真值)期丰。所以顯式強制類型轉(zhuǎn)換為布爾值最常用的方法是!!群叶,因為第二個吃挑!會將結(jié)果反轉(zhuǎn)會原值:
var a = "0";
var b = [];
var c = {};
var d = "";
var e = 0;
var f = null;
var g;
!!a; // true
!!b; // true
!!c; // true
!!d; // false
!!e; // false
!!f; // false
!!g; // false
在if(..)..這樣的布爾值上下文中,如果沒有使用Boolean(..)和!!街立,就會自動隱式地進(jìn)行ToBoolean轉(zhuǎn)換舶衬。建議使用Boolean(..)和!!來進(jìn)行顯式轉(zhuǎn)換以便讓代碼更清晰易讀。
字符串和數(shù)字之間的隱式強制類型轉(zhuǎn)換
通過重載赎离,+運算符即能用于數(shù)字加法逛犹,也能用于字符串拼接:
var a = "42";
var b = "0";
var c = 42;
var d = 0;
a + b; // "420"
c + d; // 42
這里為什么會得到“420”和42兩個不同的結(jié)果呢?通常的理解是梁剔,因為某一個或者兩個操作數(shù)都是字符串虽画,所以+執(zhí)行的是字符串拼接操作。這樣解釋只對了一半荣病,實際情況要復(fù)雜得多码撰。
var a = [1,2];
var b = [3,4];
a + b; // "1,23,4"
a和b都不是字符串,但是它們都被強制轉(zhuǎn)換為字符串然后進(jìn)行拼接个盆。
根據(jù)ES5規(guī)范脖岛,如果某個操作數(shù)是字符串或者能夠通過以下步驟轉(zhuǎn)換為字符串的話,+將進(jìn)行拼接操作颊亮。如果其中一個操作數(shù)是對象(包括數(shù)組)柴梆,則首先對其調(diào)用ToPrimitive抽象操作,該抽象操作在調(diào)用[[DefaultValue]]终惑,以數(shù)字作為上下文绍在。
你或許注意到這與ToNumber抽象操作處理對象的方式一樣。因為數(shù)組的valueOf()操作無法得到簡單基本類型值雹有,于是它轉(zhuǎn)而調(diào)用toString()偿渡。因此上例子中的兩個數(shù)組編程了"1,2"和“3,4”。+將它們拼接后返回“1,23,4”件舵。
簡單來說就是卸察,如果+的其中一個操作數(shù)是字符串(或者通過以上步驟可以得到字符串),則執(zhí)行字符串拼接铅祸,否則執(zhí)行數(shù)字加法坑质。
有一個坑常常被提到,即[]+{}和{}和[]临梗,它們返回不同的結(jié)果涡扼,分別是“[object Object]”和0。
我們可以將數(shù)字和字符串“”相+來將其轉(zhuǎn)換為字符串:
var a = 42;
var b = a + "";
b; // "42"
再來看看從字符串強制類型轉(zhuǎn)換為數(shù)字的情況:
var a = "3.14";
var b = a - 0;
b; // 3.14
對象的-操作與+類似:
var a = [3];
var b = [1];
a - b; // 2
為了執(zhí)行減法運算盟庞,a和b都需要被轉(zhuǎn)換為數(shù)字吃沪,它們首先被轉(zhuǎn)換為字符串(通過toString()),然后再轉(zhuǎn)換為數(shù)字什猖。
布爾值到數(shù)字的隱式強制類型轉(zhuǎn)換
在將某些復(fù)雜的布爾邏輯轉(zhuǎn)換為數(shù)字加法的時候票彪,隱式強制類型轉(zhuǎn)換能派上大用場红淡。當(dāng)然這種情況并不多見,屬于特殊情況特殊處理降铸。
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
以上代碼如果有多個參數(shù)時(4個在旱、5個,甚至20個)推掸,用上面的代碼就很難處理了桶蝎。這是就可以使用從布爾值到數(shù)字(0或1)的強制類型轉(zhuǎn)換:
function onlyOne() {
var sum = 0;
for (var i = 0; i < arguments.length; i++) {
// 跳過假值,和處理0一樣谅畅,但是避免了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
同樣的功能也可以通過顯式強制類型轉(zhuǎn)換來實現(xiàn):
function onlyOne() {
var sum = 0;
for (var i = 0; i < arguments.length; i++) {
sum += Number(!!arguments[i]);
}
return sum === 1;
}
隱式強制類型轉(zhuǎn)換為布爾值
下面的情況會發(fā)生布爾值隱式強制類型轉(zhuǎn)換:
(1)if(..)語句中的條件判斷表達(dá)式登渣。
(2)for(..;..;..)語句中的條件判斷表達(dá)式(第二個)。
(3)while(..)和do..while(..)循環(huán)中的條件判斷表達(dá)式毡泻。
(4)?:中的條件判斷表達(dá)式胜茧。
(5)邏輯運算符||(邏輯或)和&&(邏輯與)左邊的操作數(shù)(作為條件判斷表達(dá)式)。
||和&&
&&和||運算符的返回值并不一定是布爾類型牙捉,而是兩個操作數(shù)其中一個的值竹揍。
var a = 42;
var b = "abc";
var c = null;
a || b; // 42
a && b; // "abc"
c || b; // "abc"
c && b; // null
下面是一個十分常見的||的用法:
function foo(a, b) {
a = a || "hello";
b = b || "world";
console.log(a + " " + b);
}
foo(); // "hello world"
foo("yeah", "yeah!"); // "yeah yeah!"
有一種用法對開發(fā)人員不常見,然而JavaScript代碼壓縮工具常用邪铲。就是如果第一個操作數(shù)為真值,則&&運算符“選擇”第二個操作數(shù)作為返回值无拗,這也叫做“守護(hù)運算符”带到,即前面的表達(dá)式為后面的表達(dá)式“把關(guān)”:
function foo() {
console.log(a);
}
var a = 42;
a && foo(); // 42
foo()只有在條件判斷a通過時才會被調(diào)用。如果條件判斷未通過英染,a&&foo()就會悄然終止(也叫做“短路”)揽惹,foo()不會被調(diào)用。這樣的用法對開發(fā)人員不太常見四康,開發(fā)人員通常使用if(a){foo();}
搪搏。
var a = 42;
var b = null;
var c = "foo";
if (a && (b || c)) {
console.log("yep");
}
這里a&&(b||c)的結(jié)果實際上是“foo”而非true,然后再由if將foo強制類型轉(zhuǎn)換為布爾值闪金,所以最后結(jié)果為true疯溺。
現(xiàn)在明白了吧,這里發(fā)生了隱式強制類型轉(zhuǎn)換哎垦。如果要避免隱式強制類型轉(zhuǎn)換就得這樣:
if (!!a && (!!b || !!c)) {
console.log("yep");
}
符號的強制類型轉(zhuǎn)換
ES6允許從符號到字符串的顯式強制類型轉(zhuǎn)換囱嫩,然而隱式強制類型轉(zhuǎn)換會產(chǎn)生錯誤:
var s1 = Symbol("cool");
String(s1); // "Symbol(cool)"
var s2 = Symbol("not cool");
s2 + ""; // TypeError
符號不能夠被強制類型轉(zhuǎn)換為數(shù)字(顯式和隱式都會產(chǎn)生錯誤),但可以被強制類型轉(zhuǎn)換為布爾值(顯式和隱式結(jié)果都是true)漏设。
寬松相等和嚴(yán)格相等
==和===都是用來判斷兩個值是否“相等”墨闲,但是它們之間有一個很重要的區(qū)別,特別是在判斷條件上郑口。
常見的誤區(qū)是“==檢查值是否相等鸳碧,===檢查值和類型是否相等”盾鳞。聽起來蠻有道理,然而還不夠準(zhǔn)確瞻离。
正確的解釋是:“==允許在相等比較中進(jìn)行強制類型轉(zhuǎn)換雁仲,而===不允許”。
相等比較操作的性能
有人覺得==會比===慢琐脏,實際上雖然強制類型轉(zhuǎn)換確實要多花點時間攒砖,但僅僅是微妙級(百萬分之一秒)的差別而已。
如果進(jìn)行比較的兩個值類型相同日裙,則==和===使用相同的算法吹艇,所以除了JavaScript引擎實際上的細(xì)微差別之外,它們之間并沒有什么不同昂拂。
如果兩個值的類型不同受神,我們就需要考慮有沒有強制類型轉(zhuǎn)換的必要,有就用==格侯,沒有就用===鼻听,不用在乎性能。
字符串和數(shù)字之間的相等比較
var a = 42;
var b = "42";
a === b; // false
a == b; // true
具體是怎么轉(zhuǎn)換联四?是a從42轉(zhuǎn)換為字符串撑碴,還是b從“42”轉(zhuǎn)換為數(shù)字?
ES5規(guī)范這樣定義:
(1) 如果Type(x) 是數(shù)字朝墩,Type(y) 是字符串醉拓,則返回x == ToNumber(y) 的結(jié)果。
(2) 如果Type(x) 是字符串收苏,Type(y) 是數(shù)字亿卤,則返回ToNumber(x) == y 的結(jié)果。
其他類型和布爾類型之間的相等比較
==最容易出錯的一個地方是true和false與其他類型之間的相等比較鹿霸。
var a = "42";
var b = true;
a == b; // false
我們都知道“42”是一個真值排吴,為什么==的結(jié)果不是true呢?
規(guī)范是這樣說的:
(1) 如果Type(x) 是布爾類型懦鼠,則返回ToNumber(x) == y 的結(jié)果钻哩;
(2) 如果Type(y) 是布爾類型,則返回x == ToNumber(y) 的結(jié)果葛闷。
所以建議憋槐,無論什么情況下都不要使用==true和==false。請注意淑趾,這里說的只是==阳仔,===true和===false不允許強制類型轉(zhuǎn)換,所以并不涉及ToNumber。
var a = "42";
// 不要這樣用近范,條件判斷不成立:
if (a == true) {
// ..
}
// 也不要這樣用嘶摊,條件判斷不成立:
if (a === true) {
// ..
}
// 這樣的顯式用法沒問題:
if (a) {
// ..
}
// 這樣的顯式用法更好:
if (!!a) {
// ..
}
// 這樣的顯式用法也很好:
if (Boolean(a)) {
// ..
}
null和undefined之間的相等比較
null和undefined之間的==也涉及隱式強制類型轉(zhuǎn)換:
(1) 如果x 為null,y 為undefined评矩,則結(jié)果為true叶堆。
(2) 如果x 為undefined,y 為null斥杜,則結(jié)果為true虱颗。
在==中null和undefined相等(它們也與其自身相等),除此之外其他值都不存在這種情況蔗喂。
也就是說在==中null和undefined是一回事忘渔,可以相互進(jìn)行隱式強制類型轉(zhuǎn)換:
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
下面是顯式的做法,其中不涉及強制類型轉(zhuǎn)換缰儿,個人覺得更繁瑣一些(大概執(zhí)行效率也會更低):
var a = doSomething();
if (a === undefined || a === null) {
// ..
}
對象和非對象之間的相等比較
ES5規(guī)定:
(1) 如果Type(x) 是字符串或數(shù)字畦粮,Type(y) 是對象,則返回x == ToPrimitive(y) 的結(jié)果乖阵;
(2) 如果Type(x) 是對象宣赔,Type(y) 是字符串或數(shù)字,則返回ToPrimitive(x) == y 的結(jié)果瞪浸。
var a = 42;
var b = [ 42 ];
a == b; // true
[42]首先調(diào)用ToPrimitive抽象操作儒将,返回“42”,變成“42”==42默终,然后又變成42==42椅棺,最后二者相等。
之前介紹過的ToPrimitive抽象操作的所有特性(如toString()齐蔽、valueOf())在這里都適用。
之前我們介紹過“拆封”床估,即“打開”封裝對象含滴,返回其中的基本數(shù)據(jù)類型值。==中的ToPromitive強制類型轉(zhuǎn)換也會發(fā)生這樣的情況:
var a = "abc";
var b = Object( a ); // 和new String( a )一樣
a === b; // false
a == b; // true
但有一些值不這樣丐巫,原因是==算法中其他優(yōu)先級更高的規(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
因為沒有對應(yīng)的封裝對象谈况,所以null和undefined不能夠被封裝,Object(null)和Object()均返回一個常規(guī)對象递胧。
NaN能夠被封裝為數(shù)字封裝對象碑韵,但拆封之后NaN==NaN返回false,因為NaN不等于NaN缎脾。
比較少見的情況
首先來看看更改內(nèi)置原生原型會導(dǎo)致哪些奇怪的結(jié)果:
1祝闻、返回其他數(shù)字:
Number.prototype.valueOf = function () {
return 3;
};
new Number(2) == 3; // true
2==3不會有這個問題,因為2和3都是數(shù)字基本類型值遗菠,不會調(diào)用Number.prototype.valueOf()方法联喘。而Number(2)涉及ToPrimitive強制類型轉(zhuǎn)換华蜒,因此會調(diào)用valueOf()。
還有更奇怪的情況:
if (a == 2 && a == 3) {
// ..
}
你也許覺得這不可能豁遭,因為a不會同時等于2和3叭喜,但“同時”一詞并不準(zhǔn)確,因為a==2在a==3之前執(zhí)行蓖谢。
如果讓a.valueOf()每次調(diào)用都產(chǎn)生副作用捂蕴,比如第一次返回2,第二次返回3闪幽,就會出現(xiàn)這樣的情況啥辨。這實現(xià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.");
}
2、假值的相等比較
"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
3卡者、極端情況
[] == ![] // true
讓我們看看!運算符都做了些什么客们?根據(jù)ToBoolean規(guī)則崇决,它會進(jìn)行布爾值的顯式強制類型轉(zhuǎn)換(同時反轉(zhuǎn)奇偶校驗位)。所以[]==![]變成了[]==false底挫。前面我們講過false==[]恒傻,最后的結(jié)果就順理成章了。
2 == [2]; // true
"" == [null]; // true
0 == "\n"; // true
42 == "43"; // false
"foo" == 42; // false
"true" == true; // false
42 == "42"; // true
"foo" == [ "foo" ]; // true
4建邓、完整性檢查
"0" == false; // true -- 暈盈厘!
false == 0; // true -- 暈!
false == ""; // true -- 暈官边!
false == []; // true -- 暈沸手!
"" == 0; // true -- 暈!
"" == []; // true -- 暈注簿!
0 == []; // true -- 暈契吉!
其中有4中情況涉及==false,之前我們說過應(yīng)該避免诡渴,應(yīng)該不難掌握【杈В現(xiàn)在剩下后面3種。
正常情況下我們應(yīng)該不會這樣來寫代碼,我們應(yīng)該不太可能會用==[]來做條件判斷租悄,而是用==""或者==0谨究,如:
function doSomething(a) {
if (a == "") {
// ..
}
}
如果不小心碰到doSomething(0)和doSomething([])這樣的情況,結(jié)果會讓你大吃一驚泣棋。
又如:
function doSomething(a,b) {
if (a == b) {
// ..
}
}
doSomething("",0) 和doSomething([],"") 也會如此胶哲。
5、安全運用隱式強制類型轉(zhuǎn)換
我們要對==兩邊的值認(rèn)真推敲潭辈,以下兩個原則可以讓我們有效地避免出錯:
- 如果兩邊的值中有true或者false鸯屿,千萬不要使用==。
- 如果兩邊的值中有[]把敢、“”或者0寄摆,盡量不要使用==。
這時最好用===來避免不經(jīng)意的強制類型轉(zhuǎn)換修赞。這兩個原則可以讓我們避開幾乎所有強制類型轉(zhuǎn)換的坑婶恼。
有一種情況下強制類型轉(zhuǎn)換是絕對安全的,那就是typeof操作柏副。typeof總是返回七個字符串之一勾邦,其中沒有空字符串。所以在類型檢查過程中不會發(fā)生隱式強制類型轉(zhuǎn)換割择。typeof x=="function"是100%安全的眷篇,和typeof x==="function"一樣。
抽象關(guān)系比較
a<b中涉及的隱式強制類型轉(zhuǎn)換不太引人注意荔泳,不過還是很有必要深入了解一下蕉饼。
比較雙方首先調(diào)用ToPrimitive,如果結(jié)果出現(xiàn)非字符串玛歌,就根據(jù)ToNumber規(guī)則將雙方強制類型轉(zhuǎn)換為數(shù)字來進(jìn)行比較昧港。
var a = [ 42 ];
var b = [ "43" ];
a < b; // true
b < a; // false
如果比較雙方都是字符串,則按字母順序來進(jìn)行比較:
var a = [ "42" ];
var b = [ "043" ];
a < b; // false
a和b并沒有被轉(zhuǎn)換為數(shù)字支子,因為ToPrimitive返回的是字符串慨飘,所以ToPrimitive返回的是字符串,所以這里比較的是“42”和“043”兩個字符串译荞,它們分別以“4”和“0”開頭。因為“0”在字母順序上小于“4”休弃,所以最后結(jié)果為false吞歼。
同理:
var a = [ 4, 2 ];
var b = [ 0, 4, 3 ];
a < b; // false
a轉(zhuǎn)換為“4,2”,b轉(zhuǎn)換為“0,4,3”塔猾,同樣是按字母順序進(jìn)行比較篙骡。
再比如:
var a = { b: 42 };
var b = { b: 43 };
a < b; // ??
結(jié)果還是false,因為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
根據(jù)規(guī)范a<=b被處理為b<a,然后將結(jié)果反轉(zhuǎn)得湘。因為b<a的結(jié)果是false杖玲,所以a<=b的結(jié)果是true。
這可能與我們設(shè)想的大相徑庭淘正,即<=應(yīng)該是“小于或者等于”摆马。實際上JavaScript中<=是“不大于”的意思(即!(a>b),處理為!(b<a))鸿吆。同理a>=b處理為b<=a囤采。
相等比較有嚴(yán)格相等桂肌,關(guān)系比較卻沒有“嚴(yán)格關(guān)系比較”畜挥。也就是說如果要避免a>b中發(fā)生隱式強制類型轉(zhuǎn)換,我們只能確保a和b為相同的類型西雀,除此之外別無他法思犁。