一看到这个刁钻问题,我竟然有点懵: (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 [@@toPrimitive]() (with "default" as hint), valueOf(), and toString() methods, in that order. Note that primitive conversion calls valueOf() before toString(), which is similar to the behavior of number coercion but different from string coercion.

来源:MDN JavaScript Reference

既然如此,咱先试试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

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.