主持人注意:请抵制编辑代码或删除此通知的冲动。空白模式可能是问题的一部分,因此不应进行不必要的篡改。如果您处于“空白是无关紧要的”阵营,您应该能够接受代码。
在JavaScript中,(a==1&&a==2&&a==3)是否有可能求值为真?
这是一家大型科技公司提出的面试问题。事情发生在两周前,但我仍在努力寻找答案。我知道我们在日常工作中从未编写过这样的代码,但我很好奇。
主持人注意:请抵制编辑代码或删除此通知的冲动。空白模式可能是问题的一部分,因此不应进行不必要的篡改。如果您处于“空白是无关紧要的”阵营,您应该能够接受代码。
在JavaScript中,(a==1&&a==2&&a==3)是否有可能求值为真?
这是一家大型科技公司提出的面试问题。事情发生在两周前,但我仍在努力寻找答案。我知道我们在日常工作中从未编写过这样的代码,但我很好奇。
当前回答
我没有看到这个答案已经发布了,所以我也会把这个加入到混合中。这与Jeff使用半宽朝鲜文空格的回答类似。
变量a=1;变量a = 2.varа=3;如果(a==1&&a == 2 && а == 3) {console.log(“你好!”)}
你可能会注意到与第二个略有不同,但第一个和第三个与肉眼完全相同。所有3个字符都是不同的:
a-拉丁文小写aa - 全宽拉丁文小写Aа-西里尔文小写A
通用术语是“同字形”:看起来相同的不同unicode字符。通常很难找到三个完全无法区分的,但在某些情况下,你可能会很幸运。A、 Α、А和Ꭺ 会更好地工作(拉丁字母A、希腊字母Alpha、西里尔字母A和切罗基字母A);不幸的是,希腊语和切罗基语小写字母与拉丁字母A:α,ꭺ, 因此对上面的片段没有帮助)。
有一整类的同字形攻击,最常见的是假域名(例如,wikipediа.org(西里尔文)vs wikipedia.org(拉丁语)),但它也可以在代码中出现;通常被称为隐藏(正如一篇评论中所提到的,[隐藏]问题现在在PPCG上已不再是话题,但在过去,这类问题会成为一种挑战)。我使用这个网站来查找用于这个答案的同字形。
其他回答
这在变量a被访问的情况下是可能的,例如,2个web工作人员通过SharedArray Buffer以及一些主脚本进行访问。可能性很低,但当代码被编译为机器代码时,网络工作人员可能会及时更新变量a,从而满足条件a==1、a==2和a==3。
这可以是web工作者和JavaScript中的SharedArray Buffer提供的多线程环境中的竞争条件的一个示例。
以下是上述的基本实现:
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)
工人.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')
})
修改器.js
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的解决方案,但不需要计数器变量。)
(() => {“使用严格”;Object.defineProperty(this,“a”{“获取”:()=>{Object.defineProperty(this,“a”{“获取”:()=>{Object.defineProperty(this,“a”{“获取”:()=>{返回3;}});返回2;},可配置:真});返回1;},可配置:真});如果(a==1&&a==2&&a==3){document.body.append(“是的,这是可能的。”);}})();
如果你曾经遇到过这样的面试问题(或者注意到代码中的一些同样意外的行为),想想什么样的事情可能会导致乍一看不可能的行为:
编码:在这种情况下,您正在查看的变量不是您认为的变量。如果您故意使用Unicode,使用同形符或空格字符使变量的名称看起来像另一个变量,则可能会发生这种情况,但也可能会意外地引入编码问题,例如,当从Web复制和粘贴包含意外Unicode码点的代码时(例如,因为内容管理系统进行了一些“自动格式化”,例如将fl替换为Unicode“LATIN小LIGATURE fl”(U+FB02))。竞争条件:可能会出现竞争条件,即代码未按开发人员预期的顺序执行的情况。竞争条件通常发生在多线程代码中,但多线程不是竞争条件的必要条件——异步就足够了(不要混淆,异步并不意味着在后台使用多个线程)。请注意,因此JavaScript也不能因为是单线程而不受竞争条件的限制。请参阅此处了解一个简单的单线程但异步的示例。然而,在单个语句的上下文中,在JavaScript中很难达到竞争条件。JavaScript与web工作者有点不同,因为您可以有多个线程@mehulmpt向我们展示了一个使用web工作者的概念证明。副作用:相等比较操作的副作用(不必像这里的示例那样明显,通常副作用非常微妙)。
这类问题可能出现在许多编程语言中,不仅仅是JavaScript,因此我们在这里没有看到经典的JavaScript WTF 1。
当然,面试问题和这里的样本看起来都很做作。但它们很好地提醒我们:
副作用会变得非常严重,一个精心设计的程序应该没有不必要的副作用。多线程和可变状态可能会有问题。不正确进行字符编码和字符串处理可能会导致严重的错误。
1例如,您可以在这里找到一个完全不同的编程语言(C#)中的示例,显示出副作用(一个明显的副作用)。
我无法抗拒——其他答案毫无疑问是正确的,但你真的无法通过以下代码:
变量aᅠ = 1.变量a=2;变量ᅠa=3;如果(aᅠ==1&&a==2&&ᅠa==3){console.log(“你好!”)}
注意if语句中奇怪的空格(我从你的问题中复制的)。它是半宽朝鲜文(对于不熟悉的人来说是朝鲜语),它是一个Unicode空格字符,ECMA脚本不会将其解释为空格字符-这意味着它是标识符的有效字符。因此,有三个完全不同的变量,一个是a后面的朝鲜文,一个在a之前,最后一个只有a。为了可读性,将空格替换为_,相同的代码如下所示:
变量a_=1;变量a=2;var_a=3;如果(a_==1&&a==2&&_a==3){console.log(“你好!”)}
查看Mathias变量名验证器的验证。如果这个奇怪的间距真的包含在他们的问题中,我确信这是一个暗示,暗示了这种答案。
不要这样做。认真地
编辑:我注意到(虽然不允许启动变量)变量名称中也允许使用零宽度连接符和零宽度非连接符-请参阅用零宽度字符混淆JavaScript-利弊?。
如下所示:
变量a=1;变量a= 2; //一个零宽度字符变量a= 3; //两个零宽度字符(也可以使用另一个)如果(a==1&a==2&&a(&&a)==3) {console.log(“你好!”)}
面试规则一;永远不要说不可能。
不需要隐藏角色的诡计。
窗口__定义Getter__('a',函数(){if(类型i!=='number'){//在全局命名空间中定义i,以便在运行此函数后不会丢失i=0;}返回++i;});如果(a==1&&a==2&&a==3){console.log(“哦,亲爱的,我们做了什么?”);}