提示:在继续阅读之前,请注意此文章最后更新于 1327 天前,其中的部分内容可能已经无效或过时。

ToPrimitive

首先贴一个ToPrimitive的概念,这是一个拆箱相关的概念。

有以下三种逻辑

  1. 是否有Symbol.toPrimitive,如果有执行此操作
  2. 如果没有Symbol.toPrimitive,会执行默认的操作,规则如下:
    1. hint不可设置,有三个值'default','number','string'
    2. default会先调用valueOf(),如果没有或出错,调用toString()
    3. 'number'会调用valueOf(),如果没有或出错,调用toString()
    4. 'string'会先调用toString(),如果没有或出错,调用valueOf()

示例:

// 展示hint为default的情况
let obj={
    toString(){return 'toString'},
    valueOf(){return {}}, // Error
    [Symbol.toPrimitive](hint){
        console.log(hint);
        // 相当于返回undefined
    }
};

+obj;       // number   NaN
`${obj}`;   // string   undefined
'1'+obj;    // default  1undefined
Number(obj);// number   NaN
String(obj);// string   undefined

// 默认[Symbol.toPrimitive]
let obj={
    toString(){return 'toString'},
    valueOf(){return 1}, // Error
};
+obj;       // 1
`${obj}`;   // toString
'1'+obj;    // 11
Number(obj);// 1
String(obj);// toString

抽象相等比较算法(x==y)

例子:

/************ 1. **********/
// 1.a
undefined == undefined; // true
// 1.b
null == null;   // true
// 1.c.i x或y之一为NaN,返回false
NaN == NaN; // false
// 1.c.iii~1.c.v,x或y之一为+0,另一为-0。或两者数值相等,返回true
+ 0 == -0;  // true
// 1.c.vi 其余返回false

// 1.e
'asd'=='asd' //true
/************ 2~11 **********/

// 2~3. x,y其中一个为null,另一为undefined返回true
undefined == null // true

// 4~5. x为Number,y为String,返回x==ToNumber(y)的结果,即number和string比,把string转为number
1 == '1' // true

// 7~8. x为Boolean,返回ToNumber(x)==y的结果
false == '' // Number(false)=>0,0=='' 命中规则4,Number('')=>0 true

// 9~10. x为String或Number, y为Object,返回x==ToPrimitive的结果
'[object Object]' == {} // true ToPrimitive的结果为'[object Object]',因此为true
let a = {
    [Symbol.toPrimitive]() {
        return 1;
    }
}
a == 1; // true

与上面等价的定义:

  • 字符串比较可以按这种方式强制执行: "" + a == "" + b
  • 数值比较可以按这种方式强制执行: +a == +b
  • 布尔值比较可以按这种方式强制执行: !a == !b

几个面试题

undefined == null // true
[]==![] // true
{}==!{} // false

第一个很简单,根据规则2,直接返回true

第二个,流程如下:
1.令x为[], y为![],计算y得false,式子变为[]==false
2.根据规则7,y为Boolean,对y进行ToNumber(y)转换,式子变为[]==0
3.根据规则10,x为Object,y为Number,需要执行ToPrimitive(x),得''==0
4.最后根据规则4,x为String,y为Number,需执行ToNumber(''),得0==0
5.结果为true

第三个进行同样的流程:
1.令x为{},y为!{},得y为false,即比较{}==false
2.根据规则7,对y进行ToNumber(y)转换,得{}==0
3.根据规则10,对x进行ToPrimitive(x)转换,得'[object Object]'==0
4.结果为false

可以进行几点验证

let a = {
    [Symbol.toPrimitive]() {
        return 0;
    }
}
a == !{};   // true 因为按照上述流程最后ToPrimitive(a)的结果为0,即0==0

let b = []
b == !b // true
b[Symbol.toPrimitive] = function () {
    return 1
}
b==!![] // true
b == !b; // false

b[Symbol.toPrimitive] = function () {
    return '[object Object]'
}
b == {} // false 因为两个都是对象,肯定是不相等的。按上面的规则也属于未列出的其它情况(返回false),写这个例子是告诉自己有时候别搞着搞着把自己绕进去了
// 设计一个a满足这个式子
a == 1 && a == 2 && a == 3

let a = {
    i: 1,
    valueOf() {
        return this.i++;
    },
    toString: function () {
        console.log('toString')
    },
}

其实知道上述规则以后,这题并不难。ToPrimitive的方式选一种即可
另一种方案

var a = [1,2,3];
a.join = a.shift;

其原理也是调用toString(),toString()调用join()

总结

核心逻辑其实就是把==两边都变成Number,然后进行比较。