很久之前箕戳,看到過這樣一種判斷
[] == ![]; // true
當(dāng)時覺得很神奇某残,翻了些博客,但也似懂非懂陵吸。今天翻看博客的時候玻墅,偶然又看見了它,感覺跟以前比更清晰了些壮虫,所以在此結(jié)合 js 類型轉(zhuǎn)換澳厢,記錄下自己的理解。
[TOC]
一旨指、分解
乍一看赏酥,確實比較容易讓人迷惑,但是復(fù)雜的東西只要能夠被分解谆构,我們就能容易地分析、理解它了框都。
由 JS 運算符優(yōu)先級可知搬素,“!” 的優(yōu)先級高于 “==”,也就是說魏保,一定程度上熬尺,我們可以將上面的語句改寫為:
[] == (![]); // true
或者,更進一步:
var a = [], b = ![];
a == b; // true
二谓罗、求值
其實到上一步的時候,你可能已經(jīng)發(fā)現(xiàn)了一個問題,沒錯伞剑,那就是:
var b = ![]; // false
其實這里就涉及到對象類型的真假值的問題驯绎。JavaScript 規(guī)定,所有的 JavaScript 的引用類型數(shù)據(jù)都是真值刻蚯,這里說“引用類型數(shù)據(jù)”而非對象類型绊含,是因為
typeof null === 'object'; // true
!null === true; // true
筆者并不想糾結(jié)于 null 是不是對象類型這樣的問題進行討論。
正如上文所述炊汹,任何引用類型的數(shù)據(jù)都是真值躬充,包括對象、函數(shù)等等:
!{} === true; // false
![] === false; // true
!function () {} === false; // true
!window === true; // false
!window.open === false; // true
注意這里說的是任何引用類型,類似 Boolean 此類的包裝函數(shù)所構(gòu)造出來的對象當(dāng)然也在此列:
!new Boolean(0) === false; // true
!new Boolean(true) === true; // false
有時我們想要將某種數(shù)據(jù)快速轉(zhuǎn)換為一個布爾值充甚,而不想因為使用 Boolean 而導(dǎo)致數(shù)據(jù)變成對象的時候以政,可以使用如下形式:
!!0 // false
!!undefined // false
!!1 // true
!!{} // true
!![] // true
!!false // false
!!null // false
!!function () {} // true
順帶一提的是,出了 ! 會導(dǎo)致真假值的判斷伴找,直接將一個值作為 if 語句的判斷條件也會如此妙蔗。
if (condition) {
// do something
}
這里只要 condition
是一個真值,if 分支下的 “do something” 處的語句就會被執(zhí)行疆瑰。最典型的坑就出在 condition
是一個
- 空數(shù)組([])
- 空的 NodeList 實例( 如 document.querySelectorAll('not-exist') )
- 空的 HTMLCollection 實例( 如 document.getElementsByTagName('not-exist') )
- 空的 jQuery 實例( 如 $('not-exist') )...
因為 document.getElementById 沒有命中的時候返回 null眉反,也即是一個假值,很多人認(rèn)為 document.getElementsByTagName穆役、jQuery 等也是如此寸五,或者認(rèn)為它們返回了一個空的數(shù)組,并認(rèn)為這個空數(shù)組“應(yīng)該”是一個假值耿币。但實際上梳杏,無論是空數(shù)組、還是空的 NodeList / HTMLCollection / jQuery 的實例淹接,它們本質(zhì)上都還是引用類型數(shù)據(jù)十性,所以它們都是真值。一個比較簡單的驗證 NodeList / HTMLCollection / jQuery 的實例是否命中的方法是讀取它們的 length 屬性塑悼,如果不為 0 劲适,則可以認(rèn)為它們命中了元素。
三厢蒜、toString / valueOf
經(jīng)過前兩步的分析霞势,我們可以將前面的判斷改寫為:
[] == false; // true
按照常規(guī)思路,引用類型的變量之間的比較斑鸦,是基于引用的比較愕贡,二者如果是相同的引用,則相等巷屿,否則不等固以。如果按照這樣的邏輯,引用類型的數(shù)據(jù)根本不可能和基礎(chǔ)類型的數(shù)據(jù)相等才對嘱巾,但是這里就真的相等了憨琳。
說到這里,就必須提到原生 JS 中 toString / valueOf 這兩個處處遍布的方法浓冒。
(一) 分類
對于不同類型的對象栽渴,js定義了多個版本的 toString 和 valueOf 方法
(1) toString:
- 普通對象,返回 "[object Object]";
- 數(shù)組稳懒,返回數(shù)組元素之間添加逗號合并成的字符串;
- 函數(shù)闲擦,返回函數(shù)的定義式的字符串;
- 日期對象慢味,返回一個可讀的日期和時間字符串;
- 正則,返回其字面量表達式構(gòu)成的字符串;
(2) valueOf:
- 日期對象墅冷,返回自1970年1月1日到現(xiàn)在的毫秒數(shù);
- 其它均返回對象本身;
toString / valueOf 兩個方法纯路,主要可用于引用類型數(shù)據(jù)的類型轉(zhuǎn)換,通過調(diào)用它們寞忿,可以將引用類型數(shù)據(jù)使用在原本應(yīng)該使用基本數(shù)據(jù)類型的地方驰唬。
(二)適用場景
原生的 toString / valueOf 分別位于對象的構(gòu)造函數(shù)的 prototype 屬性上,如果需要修改腔彰,大可直接在實例對象上直接添加 toString / valueOf 方法叫编,這樣也不會影響到原型鏈上的方法。
(1)類型轉(zhuǎn)換
1)對象=>字符串
a. 執(zhí)行toString霹抛,如果返回了一個原始值搓逾,則將其轉(zhuǎn)化為字符串
b. 否則執(zhí)行valueOf方法,如果返回了一個原始值杯拐,則將其轉(zhuǎn)化為字符串
c. 否則拋出類型錯誤
如:
var o = {};
o.toString = function () {
return 'my string';
};
String(o); // my string
2) 對象=>數(shù)字
a. 執(zhí)行valueOf霞篡,如果返回了一個原始值,如果需要端逼,則將其轉(zhuǎn)化為數(shù)字
b. 否則執(zhí)行toString朗兵,如果返回了一個原始值,則將其轉(zhuǎn)化為數(shù)字并返回
c. 否則拋出類型錯誤
var o = {};
o.valueOf = function () {
return 233;
};
Number(o); // 233
(2)比較和運算
在執(zhí)行 “>”顶滩、“<”余掖、“+”、“-” 等操作的時候诲祸,如果涉及到引用類型數(shù)據(jù)浊吏,大部分引用類型數(shù)據(jù)在運算之前,會先嘗試執(zhí)行其 valueOf 方法救氯,如果該方法返回了一個基本數(shù)據(jù)類型,則拿該返回值替代對象本身參與運算否則則嘗試執(zhí)行 toString 方法歌憨,如果該方法返回了一個基本類型數(shù)據(jù)着憨,則使用該數(shù)據(jù)參與操作;如果該方法返回的不是基本類型數(shù)據(jù)务嫡,則嘗試執(zhí)行 valueOf 方法甲抖,如果該方法返回了一個基本類型數(shù)據(jù),則使用該數(shù)據(jù)參與操作心铃;否則將提示 TypeError准谚。
var o = {};
o.toString = function () {
return 2;
}
// 此時還沒有為 o 添加 valueOf 方法
// 它將先調(diào)用繼承自 Object.prototype.valueOf 方法
// 返回值是它自身
// 于是則調(diào)用這里我們?yōu)閷嵗砑拥?toString 方法
o == 2; // true
// 這里為實例添加了 valueOf 方法
// 一開始,它就將調(diào)用我們?yōu)閷嵗砑拥?valueOf 方法
// 返回值 1 是基本類型數(shù)據(jù)
// 則再調(diào)用 toString 方法
o.valueOf = function () {
return 1;
}
o == 1; // true
o + 1; // 2
o * 5; // 5
注意前面說的是“大部分引用類型數(shù)據(jù)”去扣,唯一不遵循此規(guī)則的是 Date 類型對象柱衔。與其它引用類型數(shù)據(jù)不同的是,在比較或者計算的時候,它會先嘗試調(diào)用其 toString 方法唆铐,如果沒有返回基本數(shù)據(jù)類型才嘗試調(diào)用其 valueOf 方法哲戚。
var t = new Date();
// t 繼承自 Date.prototype 上的 toString / valueOf 都能返回基本類型數(shù)據(jù)
t.valueOf(); // 返回時間戳,如 1505438878370
t.toString(); // 時間信息字符串艾岂,如 "Fri Sep 15 2017 09:27:58 GMT+0800 (CST)"
t + 2344444; // 并不會得到一個時間戳顺少,而是 "Fri Sep 15 2017 09:27:58 GMT+0800 (CST)2344444"
所以當(dāng)你不清楚它會得到什么值的時候,請自己調(diào)用 toString / valueOf 方法王浴,后來 Date.prototype 對象上增加了一個 getTime 方法替代 valueOf 獲取時間戳脆炎,但是這個方法在 IE 存在兼容性問題,僅 IE9+ 有效氓辣。
四秒裕、再轉(zhuǎn)換
到這里,其實就很清晰了筛婉。
[] == false; // true
其實就是:
([]).toString() == false; // true
也就是:
'' == false; // true
這里就涉及了基本類型數(shù)據(jù)的隱式轉(zhuǎn)換問題了簇爆。基本依照以下規(guī)則:
- 兩個都是數(shù)值爽撒,則比較數(shù)值
- 兩個都是字符串入蛆,則比較字符編碼值
- 其中一個是數(shù)值,則要把另個轉(zhuǎn)化成數(shù)值進行比較
- 如果其中一個是對象硕勿,則調(diào)用 valueOf / toString 方法
- 如果有一個是布爾值哨毁,則將其轉(zhuǎn)化成數(shù)值
顯然這里滿足最后一條規(guī)則,比較的時候源武,其實將會嘗試將二者轉(zhuǎn)化為數(shù)字類型扼褪。相當(dāng)于:
Number('') == Number(false); // true
即:
0 == 0; // true
五、總結(jié)
JavaScript 是一門弱類型語言粱栖,但是弱類型并不代表沒有類型话浇,相反的是,JavaScript 是一門類型豐富的語言闹究,除了常見語言的數(shù)字幔崖、字符串、布爾渣淤、對象赏寇、函數(shù)、null 等价认,更是有一個神奇的 undefined 類型嗅定。一邊是弱類型,一邊又是多種類型用踩,這看似矛盾渠退,但由于隱式類型轉(zhuǎn)換的存在忙迁,這種矛盾看起來又如此的合理。
P.S. 雖然上面的代碼中智什,我使用了大量的 “==”动漾,而非 “===”,但這僅是學(xué)習(xí)用的荠锭。實際開發(fā)的時候旱眯,我也推薦使用 “===”。
一方面证九,如果由于自己的疏忽删豺,沒能正確處理好隱式類型轉(zhuǎn)換,往往會造成意料之外的問題愧怜,為項目帶來潛在的風(fēng)險呀页,比如我想驗證某個變量是否是 undefined,如果采用:
value == undefined;
但實際上拥坛,null 也會被匹配進來蓬蝶,可能造成潛在的風(fēng)險,如果使用 “===” 就不會有這個問題猜惋;
另一方面丸氛,如果多人協(xié)作開發(fā),隱式類型轉(zhuǎn)換往往會為其他人帶來困擾著摔,尤其是在成員間開發(fā)能力參差不齊的情況下缓窜。
例如,我想驗證一個值是否是布爾值 true谍咆,但是我寫了這樣的代碼:
value == true;
你知道哪些數(shù)據(jù)會匹配成功么禾锤?