# 写在前面

我和这个问题也算是有段因缘了,在初学 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 的历史遗留问题了。

// 当出现 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:得到函数的字符串