什么!?a == 'juejin' && a == 666 && a == 888 为true?

1,112 阅读4分钟

看到这个标题,很多人可能会感觉很新奇, a == 'juejin' && a == 666 && a == 888 为 true,这怎么可能呢?javascript 有时就是这么的神奇,上面表达式的实现是通过 js 隐式转换部分知识实现的,那让我们先来了解一下隐式转换,再去尝试实现上述表达式吧。

什么是隐式转换

在 javascript 中,当运算符在运算时,如果两边数据类型不统一,CPU就无法进行运算,这时 javascript 会自动将运算符两边的数据做一个数据类型转换,转成一样的数据类型再计算。这种无需程序员手动转换,而由编译器自动转换的方式就称为隐式转换。

当引用类型和基础类型进行运算时,会将引用类型转换为基础类型。在 javascript 中,每个引用类型都有他们的内置方法,其中有两个内置方法 valueOf()toString():

  • toString(): 返回对象的字符串表示。
  • valueOf(): 返回对象对应的字符串、数值或布尔值表示。通常与 toString()的返回值相同。 他们能够将 Object 类型隐式转换为基础类型,从而进行运算和比较。

不同类型转换规则

了解了隐式转换需要将不同类型转换为同一类型,那我们需要在了解一下不同类型转换的规则。

首先 js 目前有基本类型: number、string、boolean、undefined、null和symbol,引用类型为Object。

其中,js规定 undefined == null,且其他类型无法转换为 undefined 和 null。剩下的其他类型大多可以互相转化。

其他类型 -> number类型

string -> number

  • 如果 string 的内容为纯数字内容,则转换结果为数字
  • 如果 string 的内容不是纯数字,则转换结果为NaN
Number('1272421')  // 1272421
Number('ok111')    // NaN

boolean -> number

  • true -> 1
  • false -> 0
Number(true)   // 1
Number(false)  // 0

undefined -> number

  • undefined -> NaN
Number(undefined)   // NaN

null -> number

  • null -> 0
Number(null)   // 0

Object -> number

  • 先调用 valueOf() 方法,看能否得到一个基础类型,如能,使用 Number() 对此基础类型进行转换
  • 如调用 valueOf() 没有得到基础类型,则调用 toString() 方法看能否得到一个基础类型,如能,使用 Number() 对此基础类型进行转换;如不能,报错
// 未实现自定义 valueOf() 和 toString() 的对象
const a = { value: 1 }
Number(a)  // NaN
// 自定义了 valueOf() 返回基础类型
const b = {
    valueOf() {
        return 6
    }
}
Number(b)  // 6
// 自定义了 toString() 返回基础类型
const c = {
    toString() {
        return 7
    }
}
Number(c)  // 7
// 自定义了 valueOf() 和 toString() 都不返回基础类型
const d = {
    valueOf() {
        return {}
    },
    toString() {
        return {}
    }
}
Number(d)  // Uncaught TypeError: Cannot convert object to primitive value

其他类型 -> string

基础类型 -> string

  • 基础类型转换为string,相当于直接在外面""变成字符串内容
String(666)   // "666"
String(true)  // "true"
String(undefined)  // "undefined"
String(null) // "null"

Object -> string

  • 先调用 toString() 方法,看能否得到一个基础类型,如能,使用 String() 对此基础类型进行转换
  • 如调用 toString() 没有得到基础类型,则调用 valueOf() 方法看能否得到一个基础类型,如能,使用 String() 对此基础类型进行转换;如不能,报错
// 未实现自定义 valueOf() 和 toString() 的对象
const a = { value: 1 }
Number(a)  // "[object Object]"
// 自定义了 toString() 返回基础类型
const b = {
    valueOf() {
        return "ok"
    }
}
String(b)  // "ok"
// 自定义了 valueOf() 返回基础类型
const c = {
    toString() {
        return "hello"
    }
}
String(c)  // "hello"
// 自定义了 valueOf() 和 toString() 都不返回基础类型
const d = {
    valueOf() {
        return {}
    },
    toString() {
        return {}
    }
}
String(d)  // Uncaught TypeError: Cannot convert object to primitive value

其他类型 -> boolean

  • undefined null 0 -0 +0 NaN '' 转换结果为 false
  • 其他类型和值转换结果都为 true
Boolean(undefined) // false
Boolean({}) // true

== 比较规则

基础类型的比较

  • undefined等于null
  • string 和 number 比较时,string 转 number
  • number 和 boolean 比较时,boolean 转 number
  • string 和 boolean 比较时,两者转 number
undefined == null;    //true
'0' == 0;            //true,字符串转数字
0 == false;           //true,布尔转数字
'0' == false;       //true,两者转数字

引用类型和基础类型比较

引用类型和基础类型比较,上面提到过会通过 valueOf() 和 toString() 方法将引用类型转换为基础类型,然后进行比较。上面有提到过转换规则,引用类型转换为 number 类型优先使用 valueOf(),转换成 string 类型优先使用 toString()。

但是在使用 == 进行比较时有一点区别:

  • 和 boolean 类型比较,都转换为 true
  • 和 number 或者 string 类型比较,都优先使用 valueOf() 看能否转换为基础类型,能的话将基础类型在转换成对应的 number 或者 string 类型比较;不能的话再使用 toString() 进行转换

实现 a == 'juejin' && a == 666 && a == 888

理解了上面的隐式转换规则,那我们就可以思考如何来实现了。

我们可以自定义 valueOf(),第一次比较时返回的值为 'juejin',第二次为666,之后返回的值为888:

const a = {
 count: 0, // 记录当前是第几次比较
 valueOf() {
   this.count++;
   if(this.count === 1) {
     return 'juejin'
   } else if(this.count === 2) {
     return 666
   } else {
     return 888
   }
 }
}

console.log(a == 'juejin' && a == 666 && a == 888)  // true

如此就实现了标题中的表达式为 true。