一道面试题看js隐式类型转换

188 阅读2分钟

我们都知道js的隐式类型转换,那么他是怎么转换?

下面从一道经典的面试题开始聊一聊前端的隐式类型转换。

面试题

有一个对象 obj = {}, 使表达式(obj == 1 && obj == 2 && obj == 3)的值为true。

题目分析

首先,表达式中使用的是双等符号(不全等比较运算符),一般我们会说这个运算符不会比较运算符两边内容的类型。当运算符两边包含基础类型数据和复杂类型数据时,js内部就对对复杂类型数据做隐式的类型转换并对比值。

那么,如果想让obj可以等于1,2,3的话我们就需要对obj的类型转换做文章,让其转换后的值为数字。并且每次转换后的值要++,那么该如何操作呢?

解法一


let a = 0;
const obj = {};
obj.toString = () => {
    return ++a;
}
console.log(obj == 1 && obj == 2 && obj == 3) // true
console.log(a); // 3

解法二


let a = 0;
const obj = {};
obj.valueOf = () => {
    return ++a;
}
console.log(obj == 1 && obj == 2 && obj == 3) // true
console.log(a); // 3

解法三


let a = 0;
const obj = {};
obj[Symbol.toPrimitive] = () => {
    return ++a;
}
console.log(obj == 1 && obj == 2 && obj == 3) // true
console.log(a); // 3

隐式类型转换的方法

通过以上的三个代码示例,我们可以看到隐式类型转换时是怎么进行的。当js复杂类型转换为原始值的时候会通过调用@@toPrimitive、valueOf、toString等实例方法进行类型转换。 那么当三个方法并存时的优先级是什么样的呢?

当实例上包含Symbol.toPrimitive方法时会以最高的优先级调用该方法进行类型转换;其次会调用valueOf方法,再次调用toString方法;如果实例上并没有这些方法则根据js原型链通过原型对象查找相关方法,最终会找到顶级原型对象Object.prototype下的方法(js中各个派生类都对Object相关的实例方法做了覆盖)。

所以,三个方法的优先级是: Symbol.toPrimitive > valueOf > toString;

什么时候会发生隐式类型转换呢?

一般 + - * / % << >> >>> == 等操作时会发生隐式类型转换。 示例:

  1. console.log(1 + '2'); // string 12
  2. console.log(null + 1); // number 1
  3. console.log(+'0'); // 0
  4. console.log('21' - 1); // 20
  5. console.log(null - 1); // -1
  6. console.log(-({valueOf: () => 1})); // -1
  7. console.log('123' >>> 2); // 30

注:位运算符的转换有一定的特殊性,当字符串123 无符号右位移两位时,首先会将字符串转换为数字,并转换成2进制表示法后再进行移位运算;当字符串123为负数的时候还会有细节差异,这里不展开说明,有兴趣的可以自己试验。