看到公众号推文 ![ ]==[ ] 值是什么,原以为取反的式子肯定是false,但这个比较的确让我心头一惊Σ(っ °Д °;)っ
那今天就具体看看隐式转换是怎么进行的吧
相等运算符
一、可能会被忽略的细节
同类型的比较就不用说了,一般会被大家忽略的都是不同类型比较的时候所出现的类型隐式转换的问题。
1 == '1' //true
1 == true //true
null == undefined //true
上面的都比较好理解,但是,回归题目,可以使a==a && a==!a
这个表达式为true的a是什么呢?
来看一个让人惊讶的比较式子吧
a = []
a == !a //true
a == a //true
好了,关于题目的答案已经找到了,但为什么会是这个样子呢,就需要细究比较操作符中的隐式转换了
二、比较运算涉及类型转换时的规则
对于如何进行比较,MDN上是这么说的:
类型转换规则
当比较运算涉及类型转换时,JavaScript会按以下规则对字符串、数字、布尔或者对象类型的操作数进行操作:
- 当比较数字和字符串时,字符串会转换成数字值。JavaScript尝试将数字字面量转换为数字类型的值。
- 当其中有一个操作数为布尔值时,如果布尔值为true,为转换为1,如果为false会转换为0
- 如果一个对象与数字或者字符串比较,JavaScript会尝试返回对象的默认值,操作符会尝试通过方法valueOf和toString将对象转换为其原始值(一个字符串或者数字类型的值)。如果尝试转换失败,会产生一个错误。
注意: 当且仅当与原始值进行比较时,对象会被转换为原始值。当两个操作数均为对象时,他们会作为对象进行比较,仅当他们的引用相同对象时返回true。
先来解释一下这个式子:
[] == ![] //true
//这里 ![] 作为布尔值false会转换成 0,
// [] 也将转换成数值字面量为 0
// 上面式子就相当于 Number([]) == Number(Boolean(![]))
对象转换成原始类型时 valueOf 与 toString的优先级
上述类型转换规则3有提到,当对象与字符串或者数字进行比较时,JavaScript会尝试返回对象默认值,调用其valueOf 和 toString将对象转换为其原始值,那到底调用哪个呢,让我们来一探究竟:
let b = {
valueOf: () => 1,
toString: () => 2
},
c = {
valueOf: () => null,
toString: () => 'null'
},
d = {
valueOf: () => ({}),
toString: () => '1'
},
e = {
valueOf: () => ({}),
toString: () => ({})
}
console.log(b == 1,b == 2)
console.log(c == null, c == 'null', c == false)
console.log(d == undefined, d == 1)
console.log(e == undefined, e == null, e == 0)
// true false
// false false true
// false true
// Uncaught TypeError: Cannot convert object to primitive value
实践出真知,由上面例子我们可以看出:
1.valueOf的优先级高于toString
2.valueOf和toString返回null和undefined时返回false
3.valueOf和toString返回的不是原始值时会报错
三、ES6中的Symbol.toPrimitive(优先级最高)
ES6中的symbol.toPrimitive这个转换函数优先级高于valueOf和toString;
symbol.toPrimitive是一个内置的symbol值,它是作为对象的函数值属性存在的,当一个对象转换为一个对应的原始值时会调用此函数。
该函数被调用的时候,会传递一个参数hint,表示预期的原始型,这个参数取值可为:number、string、default 中的一个
let obj = {
[Symbol.toPrimitive]:(hint) => {
if(hint == 'string') return 1
if(hint == 'number') return 2
return 3
}
}
console.log(String(obj) == 1) //true
console.log(Number(obj) == 2) //true
console.log(obj == 3) //true
四、扩展1:如何让 a==1&&a==2&a==3
成立
了解了上述方法之后,那就可以重写对象的valueOf或者toString方法来实现了,如下:
let a = {
value:0,
valueOf:() => {
return ++a.value
}
}
console.log(a==1&&a==2&&a==3) //true
五、扩展2:如何让 a===1&&a===2&a===3
成立
1.劫持
let obj = {
value: 0
}
Object.defineProperty(obj,'a',{
get(){
return ++obj.value
}
})
obj.a === 1 && obj.a === 2 && obj.a === 3
//true
2.ES6 proxy 代理
let obj = new Proxy({},{
value:0,
get(target,key,receiver){
if(key === 'a'){
return ++this.value
}
}
})
obj.a === 1 && obj.a === 2 && obj.a === 3