帮你理理JS中的==

写在前面

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

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

先了解一下JS的变量类型

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

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

引用类型有:Object

如果你不知道Symbol是什么的话,可以看看这个 这个

理解==的运行过程

== 的比较分三种情况,我们假设 == 左边的值是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类型之间比较的规则如下

  1. 如果任意一边是NaN,返回false
  2. 其他情况按数值大小比较,相同返回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中一个是基本类型,一个是引用类型

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

  1. 如果对象上设置了Symbol.toPrimitive属性而且是个函数,直接使用这个函数的返回值作为基本类型,如果这个函数返回的不是基本类型,会直接报错
  2. 依次调用对象上的valueof和toString方法把对象转化成基本类型的值,如果valueOf不返回基本类型就调用toString方法,如果toString方法不返回基本类型会直接报错
  3. 转化成基本类型后,再使用基本类型间的比较方法进行比较

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提出来讲,是因为这个类型比较特殊,既不像基本类型也不像引用类型,下面一起来看看吧

  1. 如果一边是symbol,另一边不是,返回false
  2. 如果x和y是通过Symbol.for传入相同的参数生成的,那么返回true
  3. x和y都是类似于 Symbol.toPrimitive 这样的知名符号,而且两者是同一个知名符号时,返回true
  4. 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:得到函数的字符串




点赞

发表评论

昵称和uid可以选填一个,填邮箱必填(留言回复后将会发邮件给你)
tips:输入uid可以快速获得你的昵称和头像

Title - Artist
0:00