0%

javascript-operator-equality

Introduction

这篇文章主要介绍一下JavaScript中的比较运算符==。先看几道题,如果你都能答上来,那么可以不用看此文了。

1
2
3
4
5
6
7
8
9
console.log('abc' == String('abc'));
console.log('abc' === String('abc'));
console.log('abc' == new String('abc'));
console.log('abc' === new String('abc'));

console.log([] == []);
console.log({} == {});

console.log(NaN == NaN);

Difference between String('abc') and new String('abc')

  • String(xxx)是将xxx转换为字符串,所以它返回的是字符串,而'abc'原本就是字符串,这里相当于直接返回'abc'。typeof String(‘abc’)返回的是string。
  • new String(xxx)是创建一个字符串对象,其内容是xxx,所以它返回的是一个对象类型,不是字符串类型。typeof new String(‘abc’)返回的是object。

How == works?

==采用Loose equality算法,其过程如下:

  1. 如果两个操作数类型相同,则按如下规则比较:
    1. Object类型:当两个操作数指向同一个对象时返回true,否则返回false。
    2. String类型:当两个操作数的字符序列相同,返回true。
    3. Number类型:当两个操作数的数值相同,返回true。注意:+0和-0被认为是相等的,NaN和任何值都不相等,包括其自身。
    4. Boolean类型:当两个操作数都是true或者都是false时返回true。
    5. BigInt类型:当两个操作数的数值相同,返回true。
    6. Symbol类型:当两个操作数都是相同的Symbol值时返回true。
  2. 如果两个操作数类型不同,则按如下规则比较:
    1. 如果一个操作数是null,另一个操作数是undefined,返回true。
    2. 如果一个操作数是primitive类型,另一个操作数是Object类型,将Object类型转换为primitive类型(转换规则在此),再按如下规则比较。
    3. 到这一步时,两个操作数都是primitive类型了,按如下规则继续比较。
      1. 如果两个操作数类型相同,按照第1条规则比较。
      2. 如果一个操作数是Symbol类型,但另一个不是,返回false。
      3. 如果一个操作数是Boolean类型,将其转换为Number类型(true -> 1, false -> 0),继续往下比较。
      4. Number类型与String类型比较,将String类型转换为Number类型进行比较。转换规则在此。
      5. Number类型与BigInt类型比较,比较他们的值,如果有一个操作数是+Infinity/-Infinity/NaN,返回false。
      6. String类型与BigInt类型比较,将String类型转换为BigInt类型(用BigInt()转换),若转换失败则返回false。

类型相同时,比较好理解,我们重点梳理一下类型不同时的比较。

  1. 两个对象类型之间的比较是比较引用地址,也比较简单,无需赘述。
  2. 一个对象和一个基本类型的比较,会将对象转换为基本类型,然后再比较。
  3. 两个基本类型之间的比较,其实,最终都会归类到此类比较。

所以,我们重点讨论一下基本类型之间的比较。JS中有7种基本类型,分别是String, Number, BigInt, Boolean, Symbol, null, undefined。

  1. null和undefined之间的比较,返回true。
  2. Symbol和其他类型之间的比较,都返回false。
    至此,还剩下String, Number, BigInt, Boolean之间的比较。剩下的这几个类型比较,基本围绕Number进行。
  3. Boolean和Number之间的比较,将Boolean转换为Number,然后再比较。
  4. String和Number之间的比较,将String转换为Number,然后再比较。
  5. Boolean和String之间的比较,将他们都转换为Number然后再比较。
  6. String和BigInt之间的比较,将String转换为BigInt,然后再比较。
  7. Number和BigInt之间的比较,比较他们的值,如果有一个操作数是+Infinity/-Infinity/NaN,返回false。

注意:

  1. Loose Equality算法满足交换律,即A == B等价于B == A。
  2. document.all被视为undefined.

Examples

以下给出一些采用==进行比较的例子,我们会给出每个例子的比较规则对应的序号(参考How == works一节)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
"1" == 1; // true, 2.3.4
true == "1" // true, 2.3.3 and 2.3.4
[] == ""; // true, 2.2, 2.3.1
0 == false; // true, 2.3.3
0 == null; // false, 2.1
0 == undefined; // false, 2.1
null == undefined; // true, 2.1
0 == !!undefined; // true, 2.3.3

// 这里有一个窍门,只要两个操作数都是对象字面量,那么一定不相等。无论==还是===都不相等。
[] == []; // false, 1.1
{} == {}; // false, 1.1
{ name: 'Philip', age: 18 } == { name: 'Philip', age: 18 }; // false, 1.1

NaN == NaN; // false, 1.3, not NaN === NaN is also false.
+0 == -0; // true, 1.3

document.all == undefined; // true, 2.1
document.all == null; // true, 2.1

const number1 = new Number(3);
const number2 = new Number(3);

number1 == 3; // true, 2.3.1
number1 == number2; // false, 1.1

const object1 = {
key: "value",
};

const object2 = {
key: "value",
};

console.log(object1 == object2); // false, 1.1
console.log(object1 == object1); // true, 1.1

下面这个例子要注意:

1
2
3
const a = [1, 2, 3];
const b = "1,2,3";
a == b; // true, 2.3.1

Array.prototype.toString()方法重写了Object.prototype.toString()方法,数组转化为字符串时,会执行以下步骤:

  1. 对数组内每个元素调用toString()方法,将其转化为字符串。
  2. 将这些字符串用逗号连接起来。
  3. 返回这个字符串。

所以上面的例子就是[1, 2, 3].toString() == "1,2,3",所以返回true。
所以空数组和空字符串之间的比较也是true。

1
[] == ""; // true, 2.3.4

数组转字符串还有很多细节,详情请看这里:Array.prototype.toString()。

总结

  1. ==比较时,不要老想着转换为Boolean,其实大多数时候是转换为Number
  2. 对象字面量使用==比较时,一定是false
  3. new String(...)和String(...)完全不同,前者是创建String对象,后者是将...转换为字符串。

References

  1. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Equality