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. nullundefined之间的比较,返回true
  2. Symbol和其他类型之间的比较,都返回false
    至此,还剩下String, Number, BigInt, Boolean之间的比较。剩下的这几个类型比较,基本围绕Number进行。
  3. BooleanNumber之间的比较,将Boolean转换为Number,然后再比较。
  4. StringNumber之间的比较,将String转换为Number,然后再比较。
  5. BooleanString之间的比较,将他们都转换为Number然后再比较。
  6. StringBigInt之间的比较,将String转换为BigInt,然后再比较。
  7. NumberBigInt之间的比较,比较他们的值,如果有一个操作数是+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