【原文地址】https://javascriptweblog.wordpress.com/2011/02/07/truth-equality-and-javascript/
一篇 2011 年的老文章驳规,很贊同作者說的 you can’t master a language until you know it inside out – and fear and evasion are the enemies of knowledge. 閱讀之焦除,學習之闻坚。
先看幾個老鳥也會覺得很 confuse 的例子
if( [ 0 ] ) {
console.log( [0] == true ); // false
console.log( !![0] ); // true
}
if( 'potato' ) {
console.log( 'potato' == false ); // false
console.log( 'potato' == true ); // false
}
上面的例子看來使用 JavaScript 進行判斷的時候結果常常是出乎意料的世舰。有個好消息是已經(jīng)有標準文檔規(guī)定這些表達式應該返回怎樣的值汗茄,并且所有的瀏覽器都將遵循該規(guī)則進行實現(xiàn)答倡。
JavaScript 中關于 truth
和 equality
的求值主要有以下三種情況:
- conditional statements and operators( 條件申明和操作:
if
,? :
,||
,&&
etc ) - the equal operator( 等于操作符:
==
) - the strict equals operator( 嚴格等于操作符:
===
)
Conditional 條件聲明和操作
在 JavaScript 中董济,所有的條件申明操作遵循相同的取值規(guī)則踱阿。下面將以 if
為例解釋這個規(guī)則泽疆。
if
聲明(表達式)會使用 ES5 中定義的一個抽象方法 ToBoolean 將其聲明(表達式) 轉換為一個布爾類型的值户矢。(ecma-262 (6,7th edition 在 7.1.2, 5th edition 在 9.2 ))[http://www.ecma-international.org/ecma-262/7.0/index.html#sec-toboolean]
| 參數(shù)(表達式)類型 | 結果 |
|: --- :|: --- :|
| Undefined | false |
| Null | false |
| Boolean | 傳入?yún)?shù)值(不做類型轉換)|
| Number | +0, -0 和 NaN 會被當做是 false,其余值都是 true |
| String | 空字符串(長度為0) 會被當做是 false, 其余值都是 true |
| Object | 所有對象都會被當做是 true |
這是 JavaScript 所使用的判斷一個值是 truthy (eg: true
, 'potato'
, 36
, [12, 34]
) 或 falsey (eg: false
, 0
, ''
) 的規(guī)則殉疼。
看到這里就能明白為什么 if([0])
會允許進入后面的代碼塊執(zhí)行了 (數(shù)組是一個對象梯浪,對象都被按照 true 解析)捌年。
再來看一些或許會讓你覺得奇怪,但是卻是符合上面描述的規(guī)則的例子:
var trutheyTester = function(expr) {
return expr ? 'truthey' : 'falsey';
}
trutheyTester({}); // truthey( 對象都被當做true )
trutheyTester(false); // falsey
trutheyTester(new Boolean(false)); // truthey (new Boolean(false) 返回的是對象)
trutheyTester(''); // falsey
trutheyTester(new String('')); // truthey
trutheyTester(NaN); // falsey
trutheyTester(new Number(NaN)); // truthey
The Equals Operator(==) 等于操作符
==
型的等于操作符(以下稱為非嚴格等于)是非常自由的(liberal)挂洛。兩邊的值有可能根本就會是不同的類型礼预,但運算卻有可能返回 true 的結果。因為非嚴格等于操作會對將要進行比較運算的一邊或者兩邊進行強制類型轉換再進行比較(通常都是裝換為number型的值)虏劲。
很明顯使用 == 操作符比較不同類型的值逆瑞,且可能返回 true 的結果,很酷伙单,但同時也很危險获高。我們的某一位 JavaScript 領域專家建議完全不要使用 ==
運算符。
這種避免使用 ==
的建議我并不是十分贊同吻育,因為我認為只有深入的學習這門語言你才能算是掌握(master)了它念秧。回避是真正掌握知識的敵人布疼。
而且摊趾,即使我們假裝 ==
操作符并不存在,你也依然無法擺脫 JavaScript 中大量存在的類型裝換問題游两。如果能夠恰當?shù)厥褂?==
進行比較砾层,將會是一個很好的工具,用來創(chuàng)建出更簡潔贱案,優(yōu)雅和可讀的代碼肛炮。
無論如何,讓我們來看看 ECMA 是如何定義 ==
運算符的標準表現(xiàn)的宝踪。它實際上并沒有想象中那么嚇人侨糟,只需要記住使用 ==
比較的時候 undefined 和 null 是相等的,大多數(shù)情況下其他類型的參數(shù)會被轉換成數(shù)字類型用來幫助進行更好的比較瘩燥。
| type(x) | type(y) | result |
|: --- :|: --- :|: --- :|
| null | undefined | true |
| undefined | null | true |
| Number | String | x == toNumber(Y) |
| String | Number | toNumber(x) == y |
| Boolean | (any) | toNumber(x) == y |
| (any) | Boolean | x == toNumber(y) |
| String or Number | Object | x == toPrimitive(y) |
| Object | String or Number | toPrimitive(x) == y |
| (any) | (any) 和 x 類型相同 | 參考 x === y
|
| 其他一些情況 | ... | false |
只要得到的結果還是一個表達式秕重,該算法就會重新執(zhí)行,直到最終返回布爾型的結果為止厉膀。
toNumber 和 toPrimitive 是兩個內置方法溶耘,按照下面的規(guī)則對傳入的參數(shù)進行轉換。
toNumber
| Argument Type | Result |
|: --- :|: --- :|
| Undefined | NaN |
| Null | +0 |
| Boolean | true -> 1, false -> +0 |
| Number | 傳入?yún)?shù)服鹅,不做轉換 |
| String | 'abc' -> NaN, '123' -> 123 |
| Object | 先使用 toPrimitive 裝換對象值凳兵,然后在對 toPrimitive 得到的值進行,toNumber 運算|
toPrimitive
| Argument Type | Result |
|: --- :|: --- :|
| Object | 在 比較運算中菱魔,如果 valueOf
方法返回一個原始類型值留荔,返回這個值。如果 toString
方法返回 原始類型值澜倦,返回這個值聚蝶。如果前面兩個方法都不返回原始類型值,拋出一個錯誤 |
| otherwise ... | 不進行類型轉換藻治,直接返回傳入的參數(shù) |
學習了上面的規(guī)則碘勉,來看幾個實際的例子:
[0] == true;
[0] == true; // false
// 偽碼
// 1. 布爾型 使用 toNumber 轉換
[0] == 1;
// 2. 對象使用 valueOf 或 toString 獲得原始類型值
'0' == 1;
// 3. string 使用 toNumber 裝換
0 == 1; // false
'potato' == true;
'potato' == true; // false
// 偽碼
// 1. 布爾型 使用 toNumber 轉換
'potato' == 1;
// 2. string 使用 toNumber 轉換
NaN == 1; // false
'potato' == false;
'potato' == false; // false
// 偽碼
// 1. 布爾型 使用 toNumber 轉換
'potato' == 0;
// 2. string 使用 toNumber 轉換
Object with valueOf
crazyNumeric = new Number(1);
crazyNumeric.toString = function() { return '2' };
crazyNumeric == 1; // true
// 偽碼
// 1. 對象 使用 valueOf 或 toString 轉換
// 1 == 1; // true
Object with toString
crazyNumeric = {
toString: function() { return '2' };
}
crazyNumeric == 1;
// 偽碼
// 1. 對象 使用 valueOf 或 toString 轉換
// 2 == 1; // false
兩邊類型裝換總是按照這樣的順序進行 Boolean > Object( valurOf > toString ) > String(譯者參考后面提供的 javascript == 操作符比較流程工具 添加的內容)
The Strict Equals Operator( === ) 嚴格等于
這種情形會比較簡單。如果是對不同類型進行比較桩卵,結果永遠都會是 false验靡。
如果比較兩邊是同一數(shù)據(jù)類型的將使用一個直觀的規(guī)則進行比較: 對象比較,必須兩邊都是同一對象的引用時才會得到 true 的結果; 字符串必須包含完全相同的字符集(contain identical character sets)才會得到 true 的結果; 其他的原始類型必須值相同才能返回 true 的結果雏节。
NaN
, null
, undefined
三者都不會存在一個嚴格相等的組合胜嗓。 NaN
甚至都不等于它自身(NaN 是 JavaScript 中唯一不等于自身的值)。
一些本不需要使用 ===
的場景
// unnecessary
if ( typeof myVar === 'function' ) { /* ... */ }
// better
if ( typeof myVar == 'function' ) { /* ... */ }
typeof
表達式返回一個 string 類型的值钩乍,這個操作永遠都是比較兩個字符串辞州。因此==
在這種情況下永遠都能得到正確的比較結果。
// unnecessary
var missing = ( myVar === undefined || myVar === null );
// better
var missing = ( myVar == null );
==
中undefined
和null
彼此相等寥粹。
在某些 ES 實現(xiàn)中变过,undefined
是可以修改的, 因此和 null 進行比較更為安全。
// unnecessary
if ( myArr.length === 3 ) { /* ... */ }
// better
if ( myArr.length == 3 ) { /* ...*/ }
都是數(shù)字的比較涝涤,
==
在這種情況下永遠都能得到正確的比較結果媚狰。
擴展閱讀:
- Peter van der Zee: JavaScript coercion tool
一個很好的==
比較流程總結,并提供一個javascript == 操作符比較流程工具以供更好的理解阔拳。 - Andrea Giammarchi: JavaScript Coercion Demystified
- ECMA-262 5th Edition