嚴格和寬鬆他們之間最重要的差異就是如何判斷相等。
普遍的誤解:
==
:檢查值的相等性。===
:檢查值與型別的相等性。但正確的應該是:
==
:允許相等性比較中的強制轉型。===
:不允許強制轉型。思考一下誤解觀念和正確觀念之間的差異。
誤解的想法:嚴格比較似乎做比較多事。
正確的想法:寬鬆比較才是做比較多事,因為型別不同還需要依照步驟強制轉型。
而這在效能的比較只有微秒的差異,選用運算子應該判斷當下是否需要強制轉型。
var a = 42;
var b = "42";
a === b; // false
a == b; // true
嚴格等於不會強制轉型,所以第一組是 false,
那在寬鬆等於這邊是怎麼運算呢?
ECMA 5 11.9.3:
如果 Type(x) 是 Number 而 Type(y) 是 String, 返回比较 x == ToNumber(y)的结果。
如果 Type(x) 是 String 而 Type(y) 是 Number, 返回比较 ToNumber(x) == y的结果。
所以第二組相等被強制轉型成數字做運算,就是ToNumber
的抽象運算。
var a = "42";
var b = true;
a == b; // false
而 42 是 truthy 的值,為何結果會 false 呢?
ECMA 5 11.9.3:
如果 Type(x) 是Boolean, 返回比较 ToNumber(x) == y 的结果。
如果 Type(y) 是Boolean, 返回比较 x == ToNumber(y) 的结果。
轉型過程是這樣:
true
根據上面規格被轉型為1
'42'
理所當然被轉型為 42
而 false 理所當然被轉為0
。
永遠不要用 == true
和 == false
可以嘗試以下判斷:
var a = "42";
// 不好(會失敗的!):
if (a == true) { // .. }
// 也不該(會失敗的!):
if (a === true) { // .. }
// 足夠好(隱含地工作):
if (a) { // .. }
// 更好(明確地工作):
if (!!a) { // .. }
// 也很好(明確地工作):
if (Boolean( a )) { // .. }
ECMA 5 11.9.3:
如果 x 是 null 而 y 是 undefined,返回 true。
如果 x 是 undefined 而 y 是 null,返回 true。
所以 null
和 undefined
在寬鬆相等會等於彼此,但不會等於整個語言的其他值。
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 之間的強制轉型是安全的。
var a = doSomething();
if (a == null) {
// ..
}
這樣 a 除了 null 和 undefined ,就算是其他 falsy 值都不會過。
ECMA 5 11.9.3:
如果 Type(x) 是一个 String 或者 Number 而 Type(y) 是一个Object, 返回比较 x == ToPrimitive(y) 的结果。
如果 Type(x) 是一个 Object 而 Type(y) 是 String 或者 Number, 返回比较 ToPrimitive(x) == y 的结果。
沒有布林是因為布林會先被強制轉型為數字
var a = 42;
var b = [ 42 ];
a == b; // true
步驟:
'42'
解封裝與 ToPrimitive 強制轉型有關:
var a = "abc";
var b = Object( a ); // 與`new String( a )`相同
a === b; // false
a == b; // true
a == b
是 true,因為經過 ToPrimitive 強制轉型。
解封裝就是解開基本型別值周圍的包裹器,回傳底層的基底值。
例如new String( a )
回傳abc
。
==
有其他優先的規則:
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 無法被封裝,沒有對應的物件包裹器,所以一樣會產生一個普通的物件,而 NaN 可封裝他的 Number 包裹器,但在 ==
解封後會失敗,因為 NaN 永遠不與自己相等。
檢視修改內建的原生原型會產生什麼結果。
Number.prototype.valueOf = function() {
return 3;
};
new Number( 2 ) == 3; // true
因為 2 或 3 兩個都是基本型別值,所以可以直接比較,並不會用到 valueOf
,但new Number( 2 )
必須經過 ToPrimitive 強制轉型,因此會呼叫valueOf
。
if (a == 2 && a == 3) {
// ..
}
...不會有變數等於 2 又等於 3,所以這段程式沒有任何意義。
var i = 2;
Number.prototype.valueOf = function() {
return i++;
};
var a = new Number( 42 );
if (a == 2 && a == 3) {
console.log( "Yep, this happened." );
}
這段程式碼有 side-effect,每次產生的結果都不相同。
列出 Falsy 的比較:
"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
""
和"NaN"
是根本不可能相等的值。"0"
和0
是合理相等的。[] == ![]; // true
!
運算子優先性大於==
[] == Boolean(![])
'' == false
0 == 0
2 == [2]; // true
"" == [null]; // true
[2]
及[null]
會經過 ToPrimitive 的強制轉型2 == '2'
而 '' == ''
0 == "\n"; // true
經由 ToNumber 轉型後 "\n"
會等於""
。
42 == "43"; // false
"foo" == 42; // false
//NaN == 42
"true" == true; // false
//NaN == 1
42 == "42"; // true
"foo" == [ "foo" ]; // true
"0" == false; // true -- 噢!
false == 0; // true -- 噢!
false == ""; // true -- 噢!
false == []; // true -- 噢!
"" == 0; // true -- 噢!
"" == []; // true -- 噢!
0 == []; // true -- 噢!
扣除掉永遠都該避免使用的東西後:
"" == 0; // true -- 噢!
"" == []; // true -- 噢!
0 == []; // true -- 噢!
==
。[]
、""
或 0 ,就考慮不要用==
。var a = [ 42 ];
var b = [ "43" ];
a < b; // true
b < a; // false
var a = [ "42" ];
var b = [ "043" ];
a < b; // false
ToPrimitive 轉型後皆為字串,如果兩者都是字串,就會開始逐字比較。
同樣邏輯也適用於:
var a = [ 4, 2 ];
var b = [ 0, 4, 3 ];
a < b; // false
此範例也為逐字比較。
var a = { b: 42 };
var b = { b: 43 };
a < 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;
先估算 b < a
再否定其結果,所以 false 的反向是 true。var a = [ 42 ];
var b = "043";
a < b; // false -- 字串比较!
Number( a ) < Number( b ); // true -- 數字比较!