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


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

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

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


2253
2018-01-15 20:20


起源


这是一个很棒的面试问题。我自己从来没有遇到过这个(或者像这个一个),但这比我听说过在链表中找到循环要好得多。它仍然是卑鄙的,我不会仅仅因为这个问题而取消某人的资格,但我当然会支持那些能够认识到某些事情可能正在发生的候选人。 - Draco18s
如果这有点代表那种公司代码库中存在的代码......运行️ - FeifanZ
这简直是​​一个非常糟糕的面试问题...... - ghord
对那些显然投票赞成这个的人来说 太宽泛:这是一个挖掘Javascript,说有太多有效的答案? - tomsmeding
主持人说明: Stack Overflow有一段历史,人们会用不同语言的答案来讨论这个问题。这些 是 试图回答这个问题,因为它们是一般问题的解决方案,尽管是用不同的语言。请不要将它们标记为“不是答案”。话虽如此,请不要以不同语言发布更多答案 - 这个问题是针对JavaScript的原因,正如其他一些答案中的评论所指出的那样,我们有理由喜欢我们特定于语言的问题保持这样。 - BoltClock♦


答案:


如果你利用 怎么样 == 作品,你可以简单地用自定义创建一个对象 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然后,引擎会尝试将字符串转换为给出相同最终结果的数字,但路径稍长。


3097
2018-01-15 20:35



你能通过改变隐含来实现这一目标吗? valueOf() 操作? - Sterling Archer
是的,valueOf代替toString也是出于同样的原因 - Kevin B
评论不适用于扩展讨论;这次谈话已经开始了 转移到聊天。 - deceze♦
根据 这个 首先会尝试转换数字 valueOf 稍微好一些。 - Salman A
@Pureferret左侧的等式比较是一个对象,而不是一个数字。那个对象有一个数字属性 i 不打扰发动机。 ;) - tomsmeding


我无法抗拒 - 其他答案无疑是正确的,但你真的无法超越以下代码:

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

请注意中的奇怪间距 if 声明(我从你的问题中复制)。它是半角度Hangul(对于那些不熟悉的人来说是韩语),它是一个Unicode空格字符,ECMA脚本不将其解释为空格字符 - 这意味着它是标识符的有效字符。因此,有三个完全不同的变量,一个是在a之后的Hangul,一个是在它之前,最后一个只是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!")
}


1917
2018-01-16 05:14



从原问题中的奇数间距来判断,我认为这完全是面试问题所寻求的答案 - 利用看起来像空格的非空格字符。好点! - Baracus
@Baracus RonJohn注意到他对Kevin的答案的评论中出现了奇怪的间距,这让我想起了这种(糟糕的)技巧,所以我不能因为发现它而受到赞扬。我有点惊讶没有人已经回答过这个问题,因为几年前因为某个博客文章而在我的工作中走了一段时间 - 我有点认为这是非常常见的知识。 - Jeff
当然,这是被禁止的 标准的漏洞,这也适用于采访。 [引证需要] - Sanchises
考虑到原始间距,它可能更糟,即变量 var ᅠ2 = 3 已经用过;所以有三个变量 aᅠᅠ= 1, ᅠ2 = 3, a = 3 (a␣ = 1, ␣2 = 3, a = 3, 以便 (a␣==1 && a==␣2 && a==3))... - Holger
@EdmundReed:我可以想象一个错误的角色进入源代码,然后每个人都有一个工作的恶魔试图找到错误。 (特别是如果有人在韩国编码。) - Martin Bonner


有可能的!

var i = 0;

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

这使用了一个内部的getter with 声明让 a 评估三个不同的值。

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


567
2018-01-15 20:35



是的,我正在尝试同样的事情:)所以在采访中的正确答案是,“它不会发生在 我的 代码因为我从不使用 with“。 - Pointy
@Pointy - 而且,我在严格模式下编程 with 不允许。 - jfriend00
@Pointy在接受的答案中他们做了类似的事情而没有 with 所以它可能发生 - Jungkook
@JonasW。很多人还在使用 == 但我没见过 with 因为...实际上从来没有在JS文档之外,它说“请不要使用它”。无论如何,一个很好的解决方案 - wortwart
@Pointy你可以不用 with: stackoverflow.com/a/48288170/65387 - mpen


没有getter或valueOf的示例:

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

这是因为 == 所调用 toString 哪个叫 .join 对于阵列。

使用另一个解决方案 Symbol.toPrimitive 这是ES6的等价物 toString/valueOf

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

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


431
2018-01-17 11:37



without valueOf,嗯...它更间接,但基本上是一回事。 - Jonas Wilms
我真的很喜欢这个解决方案,因为你不会覆盖任何东西,除了对象拥有的连接函数,它只是一个非常简洁易读的hack,使得逻辑评估为true。 - Alex Pedersen
老实说,我认为这是最好的答案。它没有任何异常,只是设置了一些值。即使有基本的JS知识也很容易理解。做得好。 - Zac Delventhal
这非常有意义,几乎感觉很有用。 - Andrew
我知道大多数答案都是滥用 toString 要么 valueOf 但这一次让我完全失控。非常聪明,我不知道它打电话 .join内部,但它完全有道理。 - GBarroso


如果询问是否可能(不是必须),它可以要求“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;
    }
  }
}


249
2018-01-16 06:21



Horray for --bogosort-- bogogeneration? - Baldrickk
即使我知道其他解决方案,我也会故意给出这个答案,因为它回答了问题,但显然不是他们追求的问题。玩愚蠢的游戏,赢得愚蠢的奖品。 - Edmund Reed
但如果需要1000多次试验呢? - Piyin
@Piyin如果需要1000多次试验,你就赢了奖品! - Skeets
我喜欢这个答案,因为把它推到极致表明这是可能的 任何 语言,如果cpu的寄存器/缓存在程序运行时被足够的宇宙射线击中,或者如果故意执行电源故障,if条件的故障分支实际上不会跳转。 - Ponkadoodle


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

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

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

它的工作原因是自定义 valueOf 当Object与原语(如Number)比较时调用的方法。主要技巧是 a.valueOf 每次都会返回新值,因为它正在调用 exec 用正则表达式 g 标志,导致更新 lastIndex 每次找到匹配时的正则表达式所以第一次 this.r.lastIndex == 0, 它匹配 1 和更新 lastIndexthis.r.lastIndex == 1,所以下次正则表达式会匹配 2 等等。


196
2018-01-16 19:35



嗯..这是怎么回事? - Abdillah
@Abdillah一个正则表达式对象将记住它匹配的最后一个索引,调用 exec 再次将从该索引开始搜索。 MDN 不是很清楚。 - Simon Chan
我明白了,所以 this.r 正则表达式对象记住状态/索引。谢谢! - Abdillah
! - Patrick Roberts
我建议传递一个字符串 exec 但是,不是要整理的整数。 - Bergi


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

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

此答案通过定义用于检索变量的getter来滥用执行上下文中的全局作用域提供的隐式变量。


183
2018-01-15 20:37



这假定 a 属于 this 这似乎不是。如果 a 是一个局部变量(它看起来像),然后这将无法正常工作。 - jfriend00
@ jfriend00你的意思是你放了var a;某处? - jontro
是啊。引用 a == 1 暗示比 a 是某个变量,而不是属性 this。虽然有一个像globals这样的奇怪的地方,两者都可能是真的,一般来说,声明一个变量 var a 要么 let a 意味着没有 this 这让你可以访问 a 作为像你的代码假设的属性。所以,你的代码显然假设了一些奇怪的全局变量。例如,您的代码在node.js中不起作用,而在函数内部不在严格模式下。您应该指定其工作的确切情况,并可能解释其工作原理。否则,这会产生误导。 - jfriend00
@ jfriend00当然可以。不确定它会与其他已经答案相结合,增加更多的价值。将更新答案 - jontro
问题是,这“永远”是否真实。答案是肯定的,这是可能出现的情况之一: a 不是局部变量,而是使用递增的getter在全局范围内定义。 - Zac Delventhal


这在变量的情况下是可能的 a 被2个网络工作者通过SharedArrayBuffer以及一些主要脚本访问。可能性很低,但是当代码编译为机器代码时,Web工作者可能会更新变量 a 恰逢其时的条件 a==1a==2 和 a==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亿次迭代后发生:

enter image description here

第二次尝试:

enter image description here

正如我所说,机会很低,但如果有足够的时间,它就会达到最佳状态。

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


171
2018-01-17 07:39



老实说,这是最好的答案。所有其他答案都需要故意尝试做一些非常不直观的事情。这个答案实际上反映了现实世界中可能发生的事情 - 竞争条件。 - Tom Swirly
不仅如此 - 我实际上已经看到这种情况发生在现实世界中。不是问题中的确切条件,但肯定是在函数开始时检查(a == 1)和函数后面的(a == 2),并且代码符合两个条件。仅供参考,我第一次看到这种情况发生在汽车发动机控制器中,我们制定了编码标准。第二次是用于军用飞机的箔条和火炬分配系统,以及我的 这是公司的第一天 我找到了这个并修复了它,而团队的其他成员仍在讨论这个问题。 (荣誉级别:高!:) - Graham
那么,你曾经在“汽车引擎控制器”和“箔条和火炬分配器系统”上工作过,这些系统是用网络工作者的javascript编程的吗?我不认为我会再去外面。 - psaxton
@psaxton :)当然不是 - 但我们有多线程软件和共享数据。这是所有多线程软件的反模式,不是特定于Javascript或Web工作者。无论您是使用汇编语言,Brainf * ck,Visual BASIC,C还是Javascript进行编程都无关紧要 - 如果您使用多线程应用程序中的共享数据执行此操作,它 会一直 失败。 - Graham
我认为现在这是围绕@ jontro答案的精心设计的包装器。 - qntm


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

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


141
2018-01-16 11:37



请注意,使用getter的方法也适用 ===, 不只是 ==。 - Makyen
请注意,这也适用于较少的缩进。 - Evert
这太过分了... - Patrick Roberts
这个解决方案依赖于 this 是箭头函数体内的全局对象。 - Roy Tinker
@PatrickRoberts这里的其他一切都不是吗? - Midnightas