注册

你确定(a == 1 && a == 2 && a == 3)不能为true?

前言

最近遇到一个非常有意思的面试题: JavaScript中有没有可能让(a== 1 && a ==2 && a==3)返回true?

讲真刚看到这题的时候,我是用这种眼神看面试官的:你TM逗我呢? 尊重一下我可行?没10年脑血栓问不出这玩意,

59799278baa9497a96eebcdd126ddb2c~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp

但看他一脸"贱笑",一副你一定答不出来的感觉,我觉得此事定不简单...

75396ed7bfbb48b189e62fccf716e9cc~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp

障眼法我TM给跪了

咱们先不管面试官的意图是什么,具体考察的是什么知识,先来看看几种奇特的解法。

解法1:隐藏字符 + if

const if = () => !0
const a = 9

if(a == 1 && a == 2 && a == 3)
{
 console.log('前端胖头鱼') // 前端胖头鱼
}

眼见为虚

e46d20890bc343e6b78cd38690ea12b2~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp

我觉得此时你和我一样,在严重怀疑自己怕是个假前端if也能被改写?a明明是9却可以等于1、2、3

2ccf75c4219e4433a53d1f52f3128917~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp

别急,这其实是一个障眼法,只是取巧蒙蔽了我们的双眼,请看下图

1175a150053f4be9a587e4ca2ed8225d~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp

真相大白if的后面有个隐藏字符,本质上是声明了一个无论输入啥都返回true函数,而下面的代码块,更是和这个函数没半毛钱关系,怎么样都会执行!!!

{
 console.log('前端胖头鱼') // 前端胖头鱼
}

所以通过构造一个看似重写了if的代码块,仿佛真的实现了题目,实在是太骚了!!!

解法2:隐藏字符 + a变量

有了上面的经验,接下来的解法,你也不会感到奇怪了。

const aᅠ = 1
const a = 2
const ᅠa = 3

if (aᅠ == 1 && a == 2 && ᅠa == 3) {
 console.log('前端胖头鱼') // 前端胖头鱼
}

c89e1d342f07468c8bbb1ea241d0f340~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp

解法3:隐藏字符 + 数字变量

既然可以伪造三个a变量,那也可以伪造三个123变量嘛

const a = 1
const ᅠ1 = a
const ᅠ2 = a
const ᅠ3 = a

if (a == ᅠ1 && a == ᅠ2 && a == ᅠ3) {
 console.log('前端胖头鱼') // 前端胖头鱼
}

大千世界,果然眼见为虚啊!!!

91d1519196514aeab770f038be36f735~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp

再来一种奇特的解法

上面几种解法本质上都没有使 a == 1 && a == 2 && a == 3true,不过是障眼法,大家笑笑就好啦!接下来我要认真起来了...

解法4:“with”

MDN上映入眼帘的是一个警告,仿佛他的存在就是个错误,我也从来没有在实际工作中用过他,但他却可以用来解决这个题目。

13f65998415e4ab9ab6e7ca3ded827cc~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp

let i = 1

with ({
 get a() {
   return i++
}
}) {
 if (a == 1 && a == 2 && a == 3) {
   console.log('前端胖头鱼')
}
}

聪明的你甚至都不用我解释代码啥意思了。

隐式转换成解题的关键

上面给出的4种解法多少有点歪门邪道的意思,为了让面试官死心,接下来的才是正解之道,而JS中的隐式转换规则大概也是出这道题的初衷。

隐式转换部分规则

JS中使用==对两个值进行比较时,会进行如下操作:

  1. 将两个被比较的值转换为相同的类型。

  2. 转换后(等式的一边或两边都可能被转换)再进行值的比较。

比较的规则如下表(mdn

35ce1325f7104a7c8a2a016f814e6397~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp

从表中可以得到几点信息为了让(a == 1),a只有这几种:

  1. a类型为String,并且可转换为数字1('1' == 1 => true

  2. a类型为Boolean,并且可转换为数字1 (true == 1 => true)

  3. a类型为Object,通过转换机制后,可转换为数字1 (请看下文

对象转原始类型的"转换机制"

规则1和2没有什么特殊的地方,我们来看看3:

对象转原始类型,会调用内置的[ToPrimitive]函数,逻辑大致如下:

  1. 如果有Symbol.toPrimitive方法,优先调用再返回,否则进行2。

  2. 调用valueOf,如果可以转换为原始类型,则返回,否则进行3。

  3. 调用toString,如果可以转换为原始类型,则返回,否则进行4。

  4. 如果都没有返回原始类型,会报错。

const obj = {
 value: 1,
 valueOf() {
   return 2
},
 toString() {
   return '3'
},
[Symbol.toPrimitive]() {
   return 4
}
}

obj == 4 // true
// 您可以将Symbol.toPrimitive、toString、valueOf分别注释掉验证转换规则

解法5: Symbol.toPrimitive

我们可以利用隐式转换规则3完成题目(看完答案你就知道为什么啦!

const a = {
 i: 1,
[Symbol.toPrimitive]() {
   return this.i++
}
}
// 每次进行a == xxx时都会先经过Symbol.toPrimitive函数,自然也就可以实现a依次递增的效果
if (a == 1 && a == 2 && a == 3) {
 console.log('前端胖头鱼') // 前端胖头鱼
}

解法6: valueOf vs toString

当然也可以利用valueOftoString

let a = {
 i: 1,
 // valueOf替换成toString效果是一样的
 // toString
 valueOf() {
   return this.i++
}
}

if (a == 1 && a == 2 && a == 3) {
 console.log('前端胖头鱼') // 前端胖头鱼
}

解法7:Array && join

数组对象在进行隐式转换时,同样符合规则3,只是在toString时还会调用join方法。所以也可以从这里下手

let a = [1, 2, 3]

a.join = a.shift

if (a == 1 && a == 2 && a == 3) {
 console.log('前端胖头鱼') // 前端胖头鱼
}

数据劫持亦是一条出路

通过隐式转换我们做出了3种让a == 1 && a == 2 && a == 3返回true的方案,聪明的你一定想到另一种思路,数据劫持,伟大的Vue就曾使用数据劫持赢得了千万开发者的芳心,我们也试试用它来解决这道面试题

解法8:Object.defineProperty

通过劫持window对象,每次读取a属性时,都给_a 增加1

let _a = 1
Object.defineProperty(window, 'a', {
 get() {
   return _a++
}
})

if (a == 1 && a == 2 && a == 3) {
 console.log('前端胖头鱼') // 前端胖头鱼
}

解法9:Proxy

当然还有另一种劫持数据的方式,Vue3也是将响应式原理中的数据劫持Object.defineProperty换成了Proxy

let a = new Proxy({ i: 1 }, {
get(target) {
return () => target.i++
}
})

if (a == 1 && a == 2 && a == 3) {
console.log('前端胖头鱼') // 前端胖头鱼
}

最后

希望能一直给大家分享实用、基础、进阶的知识点,一起早早下班,快乐摸鱼。

作者:前端胖头鱼
来源:https://juejin.cn/post/7079936779914051615

0 个评论

要回复文章请先登录注册