写在前面

我和这个问题也算是有段因缘了,在初学JS时,就遇到了这个很蛋疼的东西,也算是JS中一个很杂的知识点,每次想起时都让我头疼不已,后来我决定花点时间去研究它,然后我查了一下《JavaScript权威指南》和《JavaScript高级程序设计》和文档,然后画了一张大致的图来帮帮助自己理解,大概就是下面那个

file

后来我发现这个图画的可能只有我自己看的懂,加上后来我学了更多的东西,对他也有了更深的了解,于是打算再写个博客分析一波这个东西。虽然这个东西,我是从来不用的,=== 它不香吗。

先了解一下JS的变量类型

JS中的变量类型大致可以分为两种,基本类型( primitive values )和引用类型( reference values )

基本类型有: Undefined, Null, Boolean, String, Symbol(es6新增), Number

引用类型有:Object

如果你不知道Symbol是什么的话,可以看看这个:http://caibaojian.com/es6/

理解==的运行过程

== 的比较分三种情况,我们假设 == 左边的值是x,右边的是y,这三种情况分别是,x和y都是基本类型,x和y都不是基本类型,x和y一个是基本类型一个是引用类型,下面分情况讨论

x和y都是基本类型

因为Symbol比较特殊,所以这里我们先不讨论Symbol的情况,留到最后再单独-讨论。

首先第一步,==看看x和y中有没有undefined或者null,如果有的话,看看另一边是不是undefined或者null,如果另一边也是undefined或者null,返回true,否则返回false==

也就是说,undefined == null是成立的,而且undefined和null只会等于他们自己或者对方,其他情况下都不相等,要问为什么的话,这是JS的历史遗留问题了。

1
2
3
4
5
// 当出现undefined或者null时,只有以下三种为真,其他都为假
undefined == null // true
undefined == undefined // true
null == null // true

好,如果现在x,y中没有undefined或者null了,x和y必然是Number,Boolean,String三者中的任意一种,这个时候,有一个非常重要的原则,那就是,==先判断两边类型是否相等,相等的话就直接比较,否则把两边不是Number类型的值转化成Number再进行比较==

Number类型之间比较的规则如下

  • 如果任意一边是NaN,返回false
  • 其他情况按数值大小比较,相同返回true,否则为false
    1
    2
    3
    4
    5
    0 == ""        // true, 空字符串转化成数字是0
    0 == false // true,布尔值转化成数字是0
    0 == "1" // false, "1"转化成的数字是1
    0 == true // false,true转化成的数字是0

有关xy都是原始值的情况先告一段落,为了方便读者理解,转化成 Number类型调用的Number()函数留到最后再讲

x和y都是引用类型

这个情况比较简单,==只要比较他们引用的是不是同一个对象就行了==只有x和y引用的是同一个对象时,才返回true

1
2
3
4
5
6
7
let x,y;
x ={};
y = x;
console.log(x == y); // true
y = {}; // 新建了一个对象给y
console.log(x == y) // false, x和y已经不是引用同一个对象了

x和y中一个是基本类型,一个是引用类型

首先我们要知道,==基本类型和引用类型是不能直接比较的,也需要类型转化==。类型转化的规则如下。

  • 如果对象上设置了Symbol.toPrimitive属性而且是个函数,直接使用这个函数的返回值作为基本类型,如果这个函数返回的不是基本类型,会直接报错
  • 依次调用对象上的valueof和toString方法把对象转化成基本类型的值,,如果valueOf不返回基本类型就调用toString方法,如果toString方法不返回基本类型会直接报错
  • 转化成基本类型后,再使用基本类型间的比较方法进行比较
1
2
3
4
5
let u = {};
u[Symbol.toPrimitive] = function (type) {
return "abc"
};
console.log(u == "abc"); // true 因为u转化成基本类型是"abc"
1
2
3
4
5
6
7
8
9
10
11
12
let u = {
// "重写"了原型上的valueOf方法
valueOf() {
return [];
},
// "重写"了原型上的toString方法
toString() {
return "abc"
}
};

console.log(u == "abc"); // true,因为u转化成基本类型是"abc"
1
2
3
4
5
6
7
8
9
10
let u = {
valueOf() {
return [];
},
toString() {
return "12"
}
};

console.log(u == 12); // true, u首先转化成"12",然后"12"被转成数字12进行比较

有关valueof和toString的一些默认值会在文末写出。

当xy任意一边是Symbol类型时

之所以把Symbol提出来讲,是因为这个类型比较特殊,既不像基本类型也不像引用类型,下面一起来看看吧

  • 如果一边是symbol,另一边不是,返回false
  • 如果x和y是通过Symbol.for传入相同的参数生成的,那么返回true
  • x和y都是类似于 Symbol.toPrimitive 这样的知名符号,而且两者是同一个知名符号时,返回true
  • x和y使用了同一个symbol对象时,返回true
1
2
3
let sym = Symbol("string");
sym == 123 // false, 情况一, 只有一方是symbol

1
2
3
4
5
6
7
let x,y,z;
x = Symbol("abc");
y = Symbol.for("abc");
z = Symbol.for("abc");
x == y // false,x和y不是同一个symbol对象
y == z // true,y和z同一个symbol对象

1
2
3
let x = Symbol.toPrimitive;
let y = Symbol.toPrimitive;
x == y // true,x和y是同一个对象
1
2
3
4
5
6
let x = Symbol("abc")
let y = Symbol("abc");
let z = x;
x == y // false, 两次Symbol()返回的不是同一个Symbol对象
x == z // true,两者使用的是同一个Symbol对象

补充

Number()的转化规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*

Number()函数的转换规则如下:
1. 如果是 Boolean 值,true 和 false 将分别被转换为 1 和 0。
2. 如果是数字值,只是简单的传入和返回。
3. 如果是 null 值,返回 0。
4. 如果是 undefined,返回 NaN。
5. 如果是字符串,遵循下列规则:
a. 如果字符串中只包含数字(包括前面带正号或负号的情况),则将其转换为十进制数值,即"1"会变成 1,"123"会变成 123,而"011"会变成 11(注意:前导的零被忽略了);
b. 如果字符串中包含有效的浮点格式,如"1.1",则将其转换为对应的浮点数值(同样,也会忽略前导零);
c. 如果字符串中包含有效的十六进制格式,例如"0xf",则将其转换为相同大小的十进制整数值;
d. 如果字符串是空的(不包含任何字符),则将其转换为 0;
e. 如果字符串中包含除上述格式之外的字符,则将其转换为 NaN。
6. 如果是对象,则调用对象的 valueOf()方法,然后依照前面的规则转换返回的值。如果转换
的结果是 NaN,则调用对象的 toString()方法,然后再次依照前面的规则转换返回的字符
串值。

*/

一些对象调用valueOf和toString的结果

其实如果你没有覆盖valueOf和toString方法的话,他们调用的一定是原型上的valueOf和toString方法

普通对象

  • valueOf:得到对象本身
  • toString:”[object Object]”

数组

  • valueOf:得到数组本身
  • toString:和数组调用join(“,”)的结果一样

正则

  • valueOf:得到正则对象本身
  • toString:转化成正则的字符串( /123/ 转化成 “/123/“ )

函数

  • valueOf:得到函数本身
  • toString:得到函数的字符串