宽松相等和严格相等

上次总结了一些强制类型转换的问题,今天来讲一讲与之相关的一个知识点:宽松相等和严格相等。
一般说法是“==检查值是否相等,===检查值和类型是否相等”,这个说法咋一听没有问题,但是还不够严谨,正确的解释应该是:“==允许在相等比较中进行强制类型转换,而===不允许”。

概述

首先有两个特例要牢记:

  • NaN 不等于 NaN
  • +0 等于 -0

还规定:两个对象(包括数组和函数)指向同一个值时即视为相等,不发生强制类型转化;==在比较两个不同类型的值时会发生隐式强制类型转化,会将其一或者两者都转换为相同的类型之后再进行比较。

字符串和数字之间的相等比较

规定:字符串和数字比较,先将字符串转换为数字之后再进行相等比较。

1
2
3
4
5
var a = 42;
var b = '42';

a === b; // false
a == b; // true

字符串b会先转换为数字之后再跟a进行比较。

其他类型和布尔类型之间的相等比较

规定:其他类型和布尔类型比较,先将布尔类型转换为数字之后再进行比较。
==最容易出错的地方就是truefalse与其他类型的比较:

1
2
3
4
5
6
var a = '42';
var b = true;
var c = false;

a == b; // false
a == c; // false

字符串a既不是true也不是false,但字符串a确实是个真值。注意,这里的==比较并不涉及对于a的ToBoolean操作,所以字符串a是真值还是假值跟 == 本身没有关系!
强烈建议:无论什么情况下都不要使用 == true== false。这里面会有很多坑,一定不要手贱使用这种比较方式。

null和undefined之间的相等比较

规定:在 ==nullundefined 相等(它们也跟其自身相等),除此之外其他值都不存在这种情况。

1
2
null == undefined;  // true
undefined == null; // true

1
2
3
4
5
6
7
8
9
10
11
12
13
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

平时代码判断中:

1
2
3
4
var a == doSomething();
if(a == null){
//..
}

条件判断 a == null 只有在 doSomething 返回 nullundefined 时才成立,除此之外其他值都不成立,包括 0false""这样的值。
如果使用显式判断,就要使用严格相等 ===

1
2
3
4
var a == doSomething();
if(a === null || a === undefined){
//..
}

对象和非对象之间的相等比较

规定:字符串或数字与对象比较,先将对象进行ToPrimitive操作后再进行比较。
注意,布尔类型与对象比较,根据上面定义的规则,布尔类型会转换为数字,再进行比较。

1
2
3
4
5
var a = 'abc';
var b = Object(a); //和 new String(a) 一样

a === b; // false
a == b; // true

a == b 结果为true,因为 b 通过 ToPrimitive 操作进行强制类型转换,并返回标量基本类型值 ‘abc’,与 a 相等。
还有一些特例:

1
2
3
4
5
6
7
8
9
10
11
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不能够被封装,Object(null)Object()均返回一个常规对象。
NaN能够被封装为数字封装对象,但拆封之后 NaN == NaN 返回false,因为NaN不自等

一些特例及比较策略

有一些比较难绕的例子,列出来希望能记住:

1
2
3
4
5
6
7
"0" ==  false;  // true
false == 0; // true
false == ""; // true
false == []; // true
"" == 0; // true
"" == []; // true
0 == []; // true

以上有四种涉及 == false,我们说过要避免,所以不难掌握。我们总结两条比较规则,就能避开以上奇葩的情况:

  • 如果两边的值中有 truefalse,千万不要使用 ==
  • 如果两边的值中有 []""0,尽量不要使用 ==,可以使用 ===

注意:相等比较不要跟if(..){..}中条件判断式弄混,如if([]){ .. }中,[]是真值,跟false == []是不同的概念,不要弄晕!

抽象关系比较

这里简单介绍一下 a < b这种大小比较。
规则:

  • 比较双方先调用ToPrimitive,如果出现非字符串,就根据 ToNumber 规则将双方强制类型转换为数字进行比较。
1
2
3
4
var a = [ 42 ];
var b = [ "43" ];
a < b; // true
b < a; // false
  • 如果比较双方都是字符串,则按字母顺序来进行比较。
1
2
3
var a = [ "42" ];
var b = [ "043" ];
a < b; // false

a和b并没有转换成数字,这里ToPrimitive返回的是字符串,进行了字符串的比较,’0’小于’4’。

1
2
3
var a = [4,2];
var b = [0,4,3];
a < b; // false

这里a转换为”4,2”,b转换为”0,4,3”,字符串比较。

1
2
3
var a = { b: 42 };
var b = { b: 43 };
a < b; // false

这里 a和b都转换成了 [Object Object],故a<b不成立,这里要注意,看下面的例子:

1
2
3
4
5
6
7
8
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 然后将结果反转。因为 b < a 的结果是 false,所以 a <= b 的结果是 true
JavaScript中 <= 是不大于的意思,即 !(a > b),处理为 !(b < a)。同理,a >= b处理为 b <= a
相等比较有严格相等,但关系比较却没有“严格关系比较”,也就是说要避免 a < b 中发生隐式强制类型转换,为了保证安全,应该对关系比较中的值进行显式强制类型转换:

1
2
3
4
var a = [ 42 ];
var b = "043";
a < b; // false 字符串的比较
Number(a) < Number(b); // true 数字比较