一看到这个刁钻问题,我竟然有点懵: (a==1 && a==2 && a==3) 是否可以为true?既然能提出这个问题,说明那肯定是可以为true的,那么怎么样实现呢?
在js里面,比较运算符对于不同类型的值会做类型转换,所以这里应该有可以操作的可能。我们先看一下非严格相等在js里面的类型转换关系:
被比较值 B | |||||||
---|---|---|---|---|---|---|---|
Undefined | Null | Number | String | Boolean | Object | ||
被比较值 A | Undefined | true |
true |
false |
false |
false |
IsFalsy(B) |
Null | true |
true |
false |
false |
false |
IsFalsy(B) |
|
Number | false |
false |
A === B |
A === ToNumber(B) |
A=== ToNumber(B) |
A== ToPrimitive(B) |
|
String | false |
false |
ToNumber(A) === B |
A === B |
ToNumber(A) === ToNumber(B) |
ToPrimitive(B) == A |
|
Boolean | false |
false |
ToNumber(A) === B |
ToNumber(A) === ToNumber(B) |
A === B |
ToNumber(A) == ToPrimitive(B) |
|
Object | false |
false |
ToPrimitive(A) == B |
ToPrimitive(A) == B |
ToPrimitive(A) == ToNumber(B) |
A === B
|
从上表可以看出,如果和数字进行比较,那么字符串和布尔值都讲转换为数字之后进行比较,显然这里操作空间不大。那剩下的就是Object
了,这里Object
需要转换为原始类型。
方法一:
我们先试试toPrimitive
:
let a = {
i: 1,
[Symbol.toPrimitive]() {
return this.i++
}
}
// 每次执行比较运算时, "Symbol.toPrimitive"会被执行一次,每次+1
console.log(a == 1 && a == 2 && a == 3) // true
方法二:
上面这个方法是直接把Object
转换为原始类型Number
,可以直接进行比较,如果这个Object
没有toPrimitive()
方法呢?那么会顺序调用valueOf()
以及toString()
方法,参考:
Objects are converted to primitives by calling its
来源:MDN JavaScript Reference[@@toPrimitive]()
(with"default"
as hint),valueOf()
, andtoString()
methods, in that order. Note that primitive conversion callsvalueOf()
beforetoString()
, which is similar to the behavior of number coercion but different from string coercion.
既然如此,咱先试试valueOf()
方法:
let a = {
i: 1,
valueOf() {
return this.i++
}
}
console.log(a == 1 && a == 2 && a == 3) // true
方法三:
同理,上面的valueOf()
还可以替换为toString()
:
let a = {
i: 1,
toString() {
return this.i++
}
}
console.log(a == 1 && a == 2 && a == 3) // true
方法四:
说到toString()
的话,我们还有个特殊的对象也可以toString()
——数组。数组在toString()
的时候默认会调用join()
方法,这个join()
方法我们也可以骚操作一下:
let a = [1, 2, 3]
a.join = a.shift
console.log(a == 1 && a == 2 && a == 3) // true
似乎到这里已经无路可走了,毕竟已经发现了四种方法。还有没有一些非常规方法呢?
方法五:
很多框架里面有用到一个方法Object.defineProperty()
,这个似乎也可以操作一下:
let _a = 1
Object.defineProperty(window, 'a', {
get() {
return _a++
}
})
console.log(a == 1 && a == 2 && a == 3) // true
方法六:
还有非常火的Proxy:
let a = new Proxy({ i: 1 }, {
get(target) {
return () => target.i++
}
})
console.log(a == 1 && a == 2 && a == 3) // true
方法七:
还有一个js中“错误的存在”with
关键词,平常也几乎不会用到的:
let i = 1
with ({
get a() {
return i++
}
}) {
console.log(a == 1 && a == 2 && a == 3) // true
}
注意:with这个关键词已经被标记为过时的,虽然部分浏览器仍然支持它。
来源:MDN JavaScript Reference