主持人注意:请抵制编辑代码或删除此通知的冲动。空白模式可能是问题的一部分,因此不应进行不必要的篡改。如果您处于“空白是无关紧要的”阵营,您应该能够接受代码。

在JavaScript中,(a==1&&a==2&&a==3)是否有可能求值为真?

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


当前回答

是的,您可以这样做,请参见以下JavaScript代码:

让a=0//创建一个变量并为其赋值如果(a!==1&&a!==2&&a!==3){console.log(“true”)}

解决方案说明:

简单地说,我们加上不等号在==符号之前,以便我们告诉语言这些值是不等于变量中的值

其他回答

这在变量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。在列表中添加越来越多的内容会降低命中率。

这是可能的!

变量i=0;具有({获取a(){返回++i;}}) {如果(a==1&&a==2&&a==3)console.log(“wohoo”);}

这在with语句中使用getter,让一个求值为三个不同的值。

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

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

变量i=0;具有({获取a(){返回++i;}}) {如果(a!==a)console.log(“是的,这是打印的。”);}

如果你曾经遇到过这样的面试问题(或者注意到代码中的一些同样意外的行为),想想什么样的事情可能会导致乍一看不可能的行为:

编码:在这种情况下,您正在查看的变量不是您认为的变量。如果您故意使用Unicode,使用同形符或空格字符使变量的名称看起来像另一个变量,则可能会发生这种情况,但也可能会意外地引入编码问题,例如,当从Web复制和粘贴包含意外Unicode码点的代码时(例如,因为内容管理系统进行了一些“自动格式化”,例如将fl替换为Unicode“LATIN小LIGATURE fl”(U+FB02))。竞争条件:可能会出现竞争条件,即代码未按开发人员预期的顺序执行的情况。竞争条件通常发生在多线程代码中,但多线程不是竞争条件的必要条件——异步就足够了(不要混淆,异步并不意味着在后台使用多个线程)。请注意,因此JavaScript也不能因为是单线程而不受竞争条件的限制。请参阅此处了解一个简单的单线程但异步的示例。然而,在单个语句的上下文中,在JavaScript中很难达到竞争条件。JavaScript与web工作者有点不同,因为您可以有多个线程@mehulmpt向我们展示了一个使用web工作者的概念证明。副作用:相等比较操作的副作用(不必像这里的示例那样明显,通常副作用非常微妙)。

这类问题可能出现在许多编程语言中,不仅仅是JavaScript,因此我们在这里没有看到经典的JavaScript WTF 1。

当然,面试问题和这里的样本看起来都很做作。但它们很好地提醒我们:

副作用会变得非常严重,一个精心设计的程序应该没有不必要的副作用。多线程和可变状态可能会有问题。不正确进行字符编码和字符串处理可能会导致严重的错误。

1例如,您可以在这里找到一个完全不同的编程语言(C#)中的示例,显示出副作用(一个明显的副作用)。

JavaScript

a==a+1

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

这意味着如果一个数字a足够大,它可以被认为等于四个连续的整数:

a=100000000000000000如果(a==a+1&&a==a+2&&a==a+3){console.log(“精度损失!”);}

的确,这并不是面试官所要求的(当a=0时不起作用),但它不涉及隐藏函数或运算符重载的任何技巧。

其他语言

作为参考,Ruby和Python中有a==1&a==2&&a==3个解决方案。稍加修改,在Java中也是可能的。

Ruby

使用自定义==:

class A
  def ==(o)
    true
  end
end

a = A.new

if a == 1 && a == 2 && a == 3
  puts "Don't do this!"
end

或增加一个:

def a
  @a ||= 0
  @a += 1
end

if a == 1 && a == 2 && a == 3
  puts "Don't do this!"
end

蟒蛇

您可以为新类定义==:

class A:
    def __eq__(self, who_cares):
        return True
a = A()

if a == 1 and a == 2 and a == 3:
    print("Don't do that!")

或者,如果你喜欢冒险,重新定义整数的值:

import ctypes

def deref(addr, typ):
    return ctypes.cast(addr, ctypes.POINTER(typ))

deref(id(2), ctypes.c_int)[6] = 1
deref(id(3), ctypes.c_int)[6] = 1
deref(id(4), ctypes.c_int)[6] = 1

print(1 == 2 == 3 == 4)
# True

它可能会出错,这取决于您的系统/解释器。

python控制台与上述代码一起崩溃,因为2或3可能在后台使用。如果您使用不太常见的整数,则可以正常工作:

>>> import ctypes
>>> 
>>> def deref(addr, typ):
...     return ctypes.cast(addr, ctypes.POINTER(typ))
... 
>>> deref(id(12), ctypes.c_int)[6] = 11
>>> deref(id(13), ctypes.c_int)[6] = 11
>>> deref(id(14), ctypes.c_int)[6] = 11
>>> 
>>> print(11 == 12 == 13 == 14)
True

Java

可以修改Java Integer缓存:

package stackoverflow;

import java.lang.reflect.Field;

public class IntegerMess
{
    public static void main(String[] args) throws Exception {
        Field valueField = Integer.class.getDeclaredField("value");
        valueField.setAccessible(true);
        valueField.setInt(1, valueField.getInt(42));
        valueField.setInt(2, valueField.getInt(42));
        valueField.setInt(3, valueField.getInt(42));
        valueField.setAccessible(false);

        Integer a = 42;

        if (a.equals(1) && a.equals(2) && a.equals(3)) {
            System.out.println("Bad idea.");
        }
    }
}

这是@Jeff的答案*的反转版本,其中使用隐藏字符(U+115F、U+1160或U+3164)创建类似于1、2和3的变量。

变量a=1;变量ᅠ1=a;变量ᅠ2=a;变量ᅠ3=a;console.log(a==ᅠ1和a==ᅠ2&&a(&&a)==ᅠ3 );

*这个答案可以通过使用零宽度非接合(U+200C)和零宽度接合(U+200D)来简化。这两个字符都允许在标识符内,但不能在开头:

变量a=1;变量a‌ = 2.变量a‍ = 3.console.log(a==1&a‌ == 2&&a(&&a)‍ == 3);/****变量a=1;var a\u200c=2;var a\u200d=3;console.log(a==1&&a\u200c==2&&a\u200d==3);****/

其他技巧也可以使用相同的思想,例如使用Unicode变体选择器创建看起来完全相同的变量(a︀ = 1.一︁ = 2.一︀ == 1和a︁ == 2; // 真)。