(a == 1 && a == 2 && a == 3)可以评估为真吗?

主持人注意:请不要编辑代码或删除此声明。空格的模式可能是问题的一部分,因此不应不必要地对其进行篡改。如果您处于 “空白无关紧要” 的阵营中,则应该能够原样接受代码。

(a== 1 && a ==2 && a==3)是否有可能在 JavaScript 中评估为true

这是一家大型科技公司提出的面试问题。它发生在两周前,但我仍在努力寻找答案。我知道我们从不在日常工作中编写此类代码,但我很好奇。

答案

如果您利用==工作原理 ,则可以简单地使用自定义toString (或valueOf )函数创建一个对象,该函数会在每次使用该对象时更改其返回的值,以使其满足所有三个条件。

const a = {
  i: 1,
  toString: function () {
    return a.i++;
  }
}

if(a == 1 && a == 2 && a == 3) {
  console.log('Hello World!');
}


起作用的原因是由于使用了松散的相等运算符。使用宽松相等时,如果其中一个操作数的类型不同于另一个,则引擎将尝试将一个转换为另一个。如果对象在左侧,数字在右侧,则它将尝试通过首先调用valueOf如果可以调用)将对象转换为数字,如果失败则将调用toString 。在这种情况下,我之所以使用toString只是因为这是我想到的, valueOf会更有意义。如果我改为从toString返回一个字符串,则引擎将尝试将该字符串转换为给我们相同最终结果的数字,尽管路径略长。

我无法抗拒 - 其他答案无疑是正确的,但是您真的无法跳过以下代码:

var aᅠ = 1;
var a = 2;
var ᅠa = 3;
if(aᅠ==1 && a== 2 &&ᅠa==3) {
    console.log("Why hello there!")
}

请注意if语句(我从您的问题中复制的)中的怪异间隔。这是半角的韩文(对于不熟悉的人来说是韩文),是 Unicode 空格字符,不会被 ECMA 脚本解释为空格字符 - 这意味着它是标识符的有效字符。因此,存在三个完全不同的变量,一个变量在 a 后面是韩文,一个变量在 a 之前,最后一个是 a。为了方便阅读,用_代替空格,相同的代码如下所示:

var a_ = 1;
var a = 2;
var _a = 3;
if(a_==1 && a== 2 &&_a==3) {
    console.log("Why hello there!")
}

在 Mathias 的变量名称验证器上检查验证 。如果他们的问题中确实包含了这个怪异的空格,我相信这暗示了这种答案。

不要这样说真的

编辑:这已经到了我的注意,(虽然不是允许启动一个变量)的零宽度木匠零宽不连字字符也允许在变量名 - 看到模糊处理 JavaScript 和零角字符 - 利弊?

如下所示:

var a= 1;
var a‍= 2; //one zero-width character
var a‍‍= 3; //two zero-width characters (or you can use the other one)
if(a==1&&a‍==2&&a‍‍==3) {
    console.log("Why hello there!")
}

有可能的!

var i = 0;

with({
  get a() {
    return ++i;
  }
}) {
  if (a == 1 && a == 2 && a == 3)
    console.log("wohoo");
}

它使用的一个 getter 中with的语句让a评估,以三个不同的值。

... 这仍然不意味着应该在真实代码中使用...

更糟糕的是,此技巧也可以通过===使用。

var i = 0;

  with({
    get a() {
      return ++i;
    }
  }) {
    if (a !== a)
      console.log("yep, this is printed.");
  }

没有吸气剂或 valueOf 的示例:

a = [1,2,3];
a.join = a.shift;
console.log(a == 1 && a == 2 && a == 3);

这是.join ,因为==调用toString ,后者会为数组调用.join

另一个解决方案,使用Symbol.toPrimitive ,它与toString/valueOf在 ES6 中等效:

let i = 0;
let a = { [Symbol.toPrimitive]: () => ++i };

console.log(a == 1 && a == 2 && a == 3);

如果询问是否可能(不是必须),则可以询问 “a” 以返回随机数。如果它顺序生成 1、2 和 3,那将是正确的。

with({
  get a() {
    return Math.floor(Math.random()*4);
  }
}){
  for(var i=0;i<1000;i++){
    if (a == 1 && a == 2 && a == 3){
      console.log("after " + (i+1) + " trials, it becomes true finally!!!");
      break;
    }
  }
}

如果没有正则表达式您无法做任何事情:

var a = {
  r: /\d/g, 
  valueOf: function(){
    return this.r.exec(123)[0]
  }
}

if (a == 1 && a == 2 && a == 3) {
    console.log("!")
}

之所以起作用,是因为将 Object 与原始值(例如 Number)进行比较时会调用自定义valueOf方法。主要技巧是a.valueOf每次a.valueOf返回新值,因为它使用g标志在正则表达式上调用exec ,这会导致每次找到匹配项时都会更新该正则表达式的lastIndex 。因此,第一次this.r.lastIndex == 0 ,它匹配1并更新lastIndexthis.r.lastIndex == 1 ,因此下一次正则表达式将匹配2 ,依此类推。

可以在全局范围内使用以下内容来实现。对于nodejs ,请在以下代码中使用global而不是window

var val = 0;
Object.defineProperty(window, 'a', {
  get: function() {
    return ++val;
  }
});
if (a == 1 && a == 2 && a == 3) {
  console.log('yay');
}

此答案通过定义获取程序以获取变量来滥用执行范围中全局范围提供的隐式变量。

例如,两个 Web 工作者通过 SharedArrayBuffer 以及一些主脚本访问变量a ,这是可能的。可能性很小,但是有可能在将代码编译为机器代码时,Web 工作人员会及时更新变量a ,因此满足条件a==1a==2a==3

这可以是由 Web Worker 和 JavaScript 中的 SharedArrayBuffer 提供的多线程环境中竞争条件的示例。

这是上面的基本实现:

main.js

// Main Thread

const worker = new Worker('worker.js')
const modifiers = [new Worker('modifier.js'), new Worker('modifier.js')] // Let's use 2 workers
const sab = new SharedArrayBuffer(1)

modifiers.forEach(m => m.postMessage(sab))
worker.postMessage(sab)

worker.js

let array

Object.defineProperty(self, 'a', {
  get() {
    return array[0]
  }
});

addEventListener('message', ({data}) => {
    array = new Uint8Array(data)
    let count = 0
    do {
        var res = a == 1 && a == 2 && a == 3
        ++count
    } while(res == false) // just for clarity. !res is fine
    console.log(`It happened after ${count} iterations`)
    console.log('You should\'ve never seen this')
})

修饰符

addEventListener('message' , ({data}) => {
    setInterval( () => {
        new Uint8Array(data)[0] = Math.floor(Math.random()*3) + 1
    })
})

在我的 MacBook Air 上,第一次尝试经过约 100 亿次迭代后,它才会发生:

在此处输入图片说明

第二次尝试:

在此处输入图片说明

就像我说的那样,机会很少,但是如果有足够的时间,它将达到条件。

提示:如果您的系统花费的时间太长。仅尝试a == 1 && a == 2并将Math.random()*3更改为Math.random()*2 。在列表中添加越来越多的内容会降低命中率。

也可以使用一系列自我覆盖的 getter:

(这类似于 jontro 的解决方案,但不需要计数器变量。)

(() => {
    "use strict";
    Object.defineProperty(this, "a", {
        "get": () => {
            Object.defineProperty(this, "a", {
                "get": () => {
                    Object.defineProperty(this, "a", {
                        "get": () => {
                            return 3;
                        }
                    });
                    return 2;
                },
                configurable: true
            });
            return 1;
        },
        configurable: true
    });
    if (a == 1 && a == 2 && a == 3) {
        document.body.append("Yes, it’s possible.");
    }
})();

或者,您可以使用一个类,并使用一个实例进行检查。

function A() {
    var value = 0;
    this.valueOf = function () { return ++value; };
}

var a = new A;

if (a == 1 && a == 2 && a == 3) {
    console.log('bingo!');
}

编辑

使用 ES6 类,它看起来像这样

class A {
  constructor() {
    this.value = 0;
    this.valueOf();
  }
  valueOf() {
    return this.value++;
  };
}

let a = new A;

if (a == 1 && a == 2 && a == 3) {
  console.log('bingo!');
}