# 写在前面
我和这个问题也算是有段因缘了,在初学 JS 时,就遇到了这个很蛋疼的东西,也算是 JS 中一个很杂的知识点,每次想起时都让我头疼不已,后来我决定花点时间去研究它,然后我查了一下《JavaScript 权威指南》和《JavaScript 高级程序设计》和文档,然后画了一张大致的图来帮帮助自己理解,大概就是下面那个
后来我发现这个图画的可能只有我自己看的懂,加上后来我学了更多的东西,对他也有了更深的了解,于是打算再写个博客分析一波这个东西。虽然这个东西,我是从来不用的,=== 它不香吗。
# 先了解一下 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 的历史遗留问题了。
// 当出现 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
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
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 方法不返回基本类型会直接报错
- 转化成基本类型后,再使用基本类型间的比较方法进行比较
let u = {}; | |
u[Symbol.toPrimitive] = function (type) { | |
return "abc" | |
}; | |
console.log(u == "abc"); //true 因为 u 转化成基本类型是 "abc" |
let u = { | |
// "重写" 了原型上的 valueOf 方法 | |
valueOf() { | |
return []; | |
}, | |
// "重写" 了原型上的 toString 方法 | |
toString() { | |
return "abc" | |
} | |
}; | |
console.log(u == "abc"); //true,因为 u 转化成基本类型是 "abc" |
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
let sym = Symbol("string"); | |
sym == 123 //false, 情况一,只有一方是 symbol |
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 对象 |
let x = Symbol.toPrimitive; | |
let y = Symbol.toPrimitive; | |
x == y //true,x 和 y 是同一个对象 |
let x = Symbol("abc") | |
let y = Symbol("abc"); | |
let z = x; | |
x == y //false, 两次 Symbol () 返回的不是同一个 Symbol 对象 | |
x == z //true,两者使用的是同一个 Symbol 对象 |
# 补充
# Number () 的转化规则
/* | |
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:得到函数的字符串