提问




  主持人备注:请拒绝编辑代码或删除此通知的冲动。空白模式可能是问题的一部分,因此不应该被不必要地篡改。如果您在空白无关紧要阵营,您应该能够接受原样。



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


这是一家大型科技公司提出的面试问题。它发生在两个星期前,但我仍然试图找到答案。我知道我们从未在日常工作中写过这样的代码,但我很好奇。

最佳参考


如果你利用==的工作方式,你可以简单地用自定义toString(或valueOf)函数创建一个对象,该函数改变每次使用时返回的内容,使其满足这三个条件。[80]




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

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



我无法抗拒 - 其他答案无疑是真的,但你真的不能超越下面的代码:




var aᅠ = 1;
var a = 2;
var ᅠa = 3;
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或valueOf的示例:




a = [1,2,3];
a.join = a.shift;
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("!")
}



可以使用全局范围中的以下内容来完成。对于nodejs,在下面的代码中使用global而不是window




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



如果2个Web工作人员通过SharedArrayBuffer以及一些主脚本访问变量a,则可以这样做。可能性很低,但是当代码编译成机器代码时,Web工作者可能及时更新变量a,因此条件a==1a==2a==3很满意。


这可以是Web工作者和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')
})


modifier.js


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


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


[81]


第二次尝试:


[82]


正如我所说,机会很低,但如果有足够的时间,它将会达到这个条件。


提示:如果您的系统需要太长时间。仅尝试a == 1 && a == 2并将Math.random()*3更改为Math.random()*2。添加越来越多的列表会降低击中的可能性。

其它参考1


使用一系列自覆盖吸气剂也可以实现这一点:


(这类似于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.");
    }
})();



我没有看到这个答案已经发布,所以我也会把这个问题扔进去。这类似于Jeff对半宽韩文空间的回答。




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



或者,您可以使用它的类和检查的实例。




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

var a = new A;

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



的JavaScript



a == a +1



在JavaScript中,没有整数,只有Number s,它们被实现为双精度浮点数。


这意味着如果Number a足够大,则可以认为它等于三个连续的整数:




a = 100000000000000000
if (a == a+1 && a == a+2 && a == a+3){
  console.log("Precision loss!");
}



是的,有可能! 😎



»JavaScript





if‌=()=>!0;
var a = 9;

if‌(a==1 && a== 2 && a==3)
{
    document.write("<h1>Yes, it is possible!😎</h1>")
}



这是@Jeff的答案*的反转版本,其中隐藏字符(U + 115F,U + 1160或U + 3164)用于创建看起来像12和[[3




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



规则第一的访谈;从不说不可能。


不需要隐藏的角色诡计。




window.__defineGetter__( 'a', function(){
    if( typeof i !== 'number' ){
        // define i in the global namespace so that it's not lost after this function runs
        i = 0;
    }
    return ++i;
});

if( a == 1 && a == 2 && a == 3 ){
    alert( 'Oh dear, what have we done?' );
}



但老实说,是否有一种方法可以评估是否真实(正如其他人所表明的那样,有多种方式),我要寻找的答案,作为进行了数百次采访的人来说,将是类似的东西:


好吧,也许在一些奇怪的情况下是的,对我来说并不是很明显......但如果我在实际代码中遇到这个问题,那么我会使用常见的调试技术来弄清楚它是如何以及为什么做它正在做的事情然后立即重构代码以避免这种情况...但更重要的是:我绝对不会首先编写代码,因为这是复杂代码的定义,我努力永远不会编写复杂的代码。


我猜一些采访者会冒犯一个显然意味着一个非常棘手的问题,但是我不介意有意见的开发人员,特别是当他们能够用合理的思想支持它并且可以将我的问题与关于自己的有意义的陈述。

其它参考2


这是另一种变体,使用数组弹出你想要的任何值。




const a = {
  n: [3,2,1],
  toString: function () {
    return a.n.pop();
  }
}

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



如果您遇到过这样的面试问题(或者在代码中注意到一些同样出乎意料的行为),请考虑哪种事情可能导致乍一看似乎不可能的行为:



  1. 编码:在这种情况下,您正在查看的变量不是您认为的变量。如果你故意乱用Unicode使用同形字符或空格字符使变量名称看起来像另一个变量,可能会发生这种情况,但编码问题也可能会偶然引入,例如:复制时粘贴来自Web的包含意外Unicode代码点的代码(例如,因为内容管理系统执行了一些自动格式化,例如用[[LATIN SMALL LIGATURE FL替换fl(U + FB02))。[86]

  2. 竞争条件:可能发生竞争条件,即代码未按开发人员预期的顺序执行的情况。竞争条件经常发生在多线程代码中,但多线程并不是竞争条件可能的要求 - 异步性就足够了(并且不要混淆,异步并不意味着在引擎盖下使用多个线程)。


    请注意,因为它是单线程的,因此JavaScript也不会没有竞争条件。请参阅此处获取简单的单线程 - 但异步 - 示例。然而,在单一陈述的背景下,竞争条件在JavaScript中很难被击中。[90]


    与Web worker的JavaScript有点不同,因为您可以拥有多个线程。 @mehulmpt向我们展示了使用网络工作者的一个很好的概念验证。

  3. 副作用:相等比较操作的副作用(不必像此处的示例那样明显,通常副作用非常微妙)。



这些问题可以出现在许多编程语言中,而不仅仅是JavaScript,所以我们没有看到这里的经典JavaScript WTF之一 1 [92]


当然,面试问题和这里的样本都看起来非常人为。但它们是一个很好的提醒:



  • 副作用可能会变得非常讨厌,精心设计的程序应该没有不必要的副作用。

  • 多线程和可变状态可能会有问题。

  • 不进行字符编码和字符串处理可能会导致令人讨厌的错误。



1 例如,你可以在一个完全不同的编程语言(C#)中找到一个展示副作用(一个明显的)的例子。 [93]

其它参考3


好吧,另一个黑客与发电机:




const value = function* () {
  let i = 0;
  while(true) yield ++i;
}();

Object.defineProperty(this, 'a', {
  get() {
    return value.next().value;
  }
});

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



实际上,问题的第一部分的答案在每种编程语言中都是是。例如,这是在C/C ++的情况下:


#define a   (b++)
int b = 1;
if (a ==1 && a== 2 && a==3) {
    std::cout << "Yes, it's possible!" << std::endl;
} else {
    std::cout << "it's impossible!" << std::endl;
}

其它参考4


相同但不同,但仍然相同(可多次测试):




const a = { valueOf: () => this.n = (this.n || 0) % 3 + 1}
    
if(a == 1 && a == 2 && a == 3) {
  console.log('Hello World!');
}

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



我认为这是实现它的最小代码:




i=0,a={valueOf:()=>++i}

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



使用代理:[94]


var a = new Proxy({ i: 0 }, {
    get: (target, name) => name === Symbol.toPrimitive ? () => ++target.i : target[name],
});
console.log(a == 1 && a == 2 && a == 3);


代理基本上假装是目标对象(第一个参数),但拦截目标对象上的操作(在本例中为get property操作),这样就有机会做除默认对象行为之外的其他操作。在这种情况下,当==强制其类型以便将其与每个数字进行比较时,在a上调用get property动作。有时候是这样的:



  1. 我们创建一个目标对象{ i: 0 },其中i属性是我们的计数器

  2. 我们为目标对象创建一个代理并将其分配给a

  3. 对于每个a ==比较,a的类型被强制为原始值

  4. 此类强制导致内部调用a[Symbol.toPrimitive]()

  5. 代理拦截使用get handler
  6. 获取a[Symbol.toPrimitive]函数
  7. 代理的获取处理程序检查所获取的属性是Symbol.toPrimitive,在这种情况下,它会递增,然后从目标对象返回计数器:++target.i。如果不同的属性是正在检索,我们回过头来返回默认属性值target[name]



所以:


var a = ...; // a.valueOf == target.i == 0
a == 1 && // a == ++target.i == 1
a == 2 && // a == ++target.i == 2
a == 3    // a == ++target.i == 3


与大多数其他答案一样,这仅适用于松散的等式检查(==),因为严格的相等性检查(===)不会执行代理可以拦截的类型强制。

其它参考5


使用符号的ECMAScript  6答案:


const a = {value: 1};
a[Symbol.toPrimitive] = function() { return this.value++ };
console.log((a == 1 && a == 2 && a == 3));


由于==的使用,JavaScript应该将a强制转换为接近第二个操作数的东西(在这种情况下123。但是在JavaScript尝试自己进行强制攻击之前,它会尝试调用Symbol.toPrimitive。如果你提供Symbol.toPrimitive JavaScript将使用函数返回的值。如果没有,JavaScript会调用valueOf[95] [96]

其它参考6


这个使用defineProperty,带来一个很好的副作用,导致全局变量!




var _a = 1

Object.defineProperty(this, "a", {
  "get": () => {
    return _a++;
  },
  configurable: true
});

console.log(a)
console.log(a)
console.log(a)



这可能是平等的主要原因是==运算符。 ===它不会起作用。


const a = {
    i: 0;
    valueOf: function() {
       return this.i++;
    }
}

if(a == 1 && a == 2 && a == 3) // true


因为在使用==运算符时将调用方法valueOftoString


if(a === 1 && a === 2 && a === 3) // false


不调用valueOftoString方法。