为什么我只用===而不用==呢?

7,528 阅读5分钟

前言

在没接触eslint之前,我的代码格式可谓是随着心情走的,爱怎么写就怎么写。自从三年前,做一个Vue项目引入了eslint后,我的代码就变得规范多了,以至于现在还产生了强迫症。eslint里面有很多代码规范的标准。一般情况下,React项目代码遵循airbnb规范,Vue项目代码遵循standard规范。不过里面都有一条细则提及:始终使用 === 替代 ==。就这样,我将这个习惯沿用到现在,但我并没有去深究过,只知道这样做能减少代码中意想不到的出错。由于我本人并不甘心于“知其然而不知其所以然”,我想深究一番=====的区别。

===和==的区别

=====本质的区别是:前者是判断运算符两边的操作数是否严格等于,并不会做任何的数据类型转换;后者是判断运算符两边的操作数是否不严格等于,会适当地进行隐式类型转换。

== 使用细则

下面给出用==运算符比较时,两边操作数xy隐式类型转换的细则:

1.xy类型相同

1.1 若x为undefined类型, 则返回true。

console.log(undefined == undefined); // true

1.2 若x为null类型,则返回true。

console.log(null == null); // true

1.3 若x为number类型,且x和y只要有一者为NaN,则返回false。(NaN并不等于本身)

console.log(NaN == 0); // false
console.log(0 == NaN); // false
console.log(NaN == 1); // false
console.log(1 == NaN); // false
console.log(NaN == NaN); // false

1.4 若x为number类型,且x和y的数值相等,则返回true。若x和y的数值不相等,则返回false。

console.log(0 == 0); // true
console.log(1 == 1); // true
console.log(0 == 1); // false

1.5 若x为number类型,且x和y的值为+0或者-0,则返回true。

console.log(-0 == +0); // true
console.log(+0 == -0); // true
console.log(+0 == +0); // true
console.log(-0 == -0); // true

1.6 若x为string类型,当且仅当x和y字符序列完全相等的,则返回true。否则,返回false。

console.log('foo' == 'foo'); //true
console.log('foo' == 'bar'); // false

1.7 若x为boolean类型,当x和y二者同为true或者false时,则返回true。否则,返回false。

console.log(true == true); // true
console.log(false == false); // true
console.log(true == false); // false
console.log(false == true); // false

1.8 若x为object类型,当且仅当x和y二者是同一引用,则返回true。否则,返回false。

var x = {}, y = {}, z = x;
console.log(x == y); // false
console.log(x == z); // true
console.log(y == z); // false
console.log(x == {}); // false
console.log({} == y); // false
console.log({} == {}); // false

2.xy类型不相同

2.1 若x为null,y为undefined,或者x为undefined,y为null,则返回true。

console.log(null == undefined); // true
console.log(undefined == null); // true

2.2 若x与y二者,一个为number类型,另一个为string类型,则先将string类型隐式转换为number类型,再进行数值比较。

console.log('123' == 123); // true  <=> Number('123') == 123  <=> 123 == 123
console.log(123 == '123'); // true  <=> 123 == Number('123')  <=> 123 == 123
console.log('abc' == 123) // false  <=> Number('abc') == 123  <=> NaN == 123

2.3 若x与y二者,若存在一个为boolean类型,则先将boolean类型隐式转换为number类型,再进行数值比较。

console.log(false == 0); // true  <=>  Number(false) == 0  <=>  0 == 0
console.log(true == 1); // true  <=>  Number(true) == 1 <=>  1 == 1
console.log(false == 2); // false  <=>  Number(false) == 2  <=> 0 == 2
console.log(true == 2); // false  <=>  Number(true) == 2  <=> 1 == 2

2.4 若x与y二者,一个为number类型或者string类型或者boolean类型,另一个为object类型时,object类型会隐式调用valueOf或者toString方法,再进行比较。

var foo = { bar: 0 };
console.log(foo == 2); // false <=> foo.toString() == 2 <=> '[object Object]' == 2  <=> Number('[object Object]') == 2  <=> NaN == 2
console.log(foo == '2'); // false <=> foo.toString() == '2' <=> '[object Object]' == '2'
console.log(foo == '[object Object]'); // true

ps:我们可以重写valueOf或者toString方法来覆盖原生方法默认的行为,来达到最佳的对比效果。

var foo = { bar: 0 };
foo.toString = () => '2'; // foo.valueOf = () => 2; 若两者都重写了,以valueOf为准
console.log(foo == 2); // true;
console.log(foo == '2'); // true

2.5 其余情况返回false。

console.log('123abc' == 123); // false
console.log(null == false); // false
console.log(undefined == false); // false
...

接着,我们探讨一下一个有趣的题目:[] == ![] // -> true,利用上面罗列的细则,我们一步步推导。

// ![]返回是一个boolean类型 -> !Boolean([]) -> !true -> false
[] == ![]  <=> [] == false
// object类型和boolean对比,先转换对象 ->  [] -> [].toString() -> ''
[] == false <=> '' == false
// ==两边操作数出现boolean类型,我们先将它做数字类型转换 -> false -> Number(false) -> 0
'' == false <=> '' == 0
// ==两边操作数出现string类型和number类型,我们先将string类型做数字类型转换 -> '' -> Number('') -> 0
'' == 0 <=> 0 == 0
// 所以最后得出的结果为 true。

可见,这些细则已经足够难记,倘若某一天我们还没去注意怎么使用==,程序中难免会出现很多意想不到的bug。为了尽量避免出错,我实际开发中,一般只会使用===,而不会使用==

=== 使用细则

接下来,我们看看,使用===的细则,这里同样用x和y代表运算符两边的操作数。

  • 若x和y类型不同,直接返回false。
console.log(undefined === null); // false
console.log(1 === true); // false
console.log(0 === false); // false
console.log(1 === '1'); // false
console.log(0 === '0'); // false
console.log('1' === true); // false
console.log('0' === false); // false
console.log(0 === []); // false
console.log(false === []); // false
console.log('' === []); // false
  • 若x和y类型相同,若都为基本类型,对比二者数值是否相等;若为引用类型,对比两者引用地址是否是同一地址。
var a = {}, b = {}, c = a;
console.log(undefined === undefined); // true
console.log(null === null); // true
console.log(0 === 0); // true
console.log(0 === 1); // false
console.log('0' === '0'); // true
console.log('0' === '1'); false
console.log(false === false); // true;
console.log(true === false); // false
console.log({} === {}); // false
console.log(a === b); // false
console.log(a === c); // true;
console.log(b === c); // false
...

我们可以看到,使用===仅有两条细则,完全不涉及到一些隐式数据类型转换,大大提高了代码的可调试性和可预见性,而且易用性远比==好。所以,在日常开发中,我强烈推荐使用===,尽可能少用==。或许我的推荐显得不怎么权威,但是,这个细则已经写入了很多JavaScript代码规范了。