ChatGPT解决这个技术问题 Extra ChatGPT

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

想要改进这篇文章?提供这个问题的详细答案,包括引文和解释为什么你的答案是正确的。没有足够细节的答案可能会被编辑或删除。

版主注意:请抵制编辑代码或删除此通知的冲动。空白的模式可能是问题的一部分,因此不应被不必要地篡改。如果您在“空白是微不足道的”阵营中,您应该能够按原样接受代码。

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

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

评论不用于扩展讨论;此对话已moved to chat
对于显然投票认为这个过于宽泛的人:这是对 Javascript 的挖掘,说有太多的有效答案?
有些人坐在那里思考什么是可能的。其他人则专注于他们是否正在为他们的客户构建可行的、业务正确的产品。 IMO,除了你不应该在面试中问这类问题或编写这类代码之外,这个问题没有任何实际用途。这就是为什么它应该被关闭。我的意思是,企业是否意识到他们付了钱让别人坐下来谈论这些事情?
阅读答案后,故事的道德是:当你的意思是 === 时不要使用 ==,有一个禁止非 ASCII 变量名的编码标准,并且有一个强制执行前两个道德的 linting 过程。
主持人注:Stack Overflow 有一段历史,人们用不同的语言回答问题。这些是回答问题的尝试,因为它们是一般问题的解决方案,尽管使用不同的语言。请不要将它们标记为“不是答案”。话虽如此,也请不要发布更多不同语言的答案 - 正如其他一些答案下的评论所指出的那样,这个问题特定于 JavaScript 是有原因的,我们喜欢我们的语言特定问题是有原因的保持这样。

u
user4020527

如果您利用 how == works,您可以简单地创建一个具有自定义 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 返回一个字符串,那么引擎会尝试将该字符串转换为一个数字,从而为我们提供相同的最终结果,尽管路径稍长。


您可以通过更改隐含的 valueOf() 操作来实现此目的吗?
是的,出于同样的原因, valueOf 可以代替 toString
评论不用于扩展讨论;此对话已moved to chat
根据 this,将首先尝试数字转换,因此 valueOf 稍好一些。
@Pureferret 等式比较的左侧是一个对象,而不是一个数字。该对象在 i 上具有数字属性不会打扰引擎。 ;)
J
Jeff

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

var aᅠ = 1;变量 a = 2;变量ᅠa = 3; if(aᅠ==1 && a== 2 &&ᅠa==3) { console.log("你好!") }

请注意 if 语句中的奇怪间距(我从您的问题中复制)。它是半角 Hangul(对于那些不熟悉的人来说是韩语),它是一个 Unicode 空格字符,ECMA 脚本不会将其解释为空格字符 - 这意味着它是标识符的有效字符。因此,有三个完全不同的变量,一个是韩文在 a 之后,一个是在它之前,最后一个是只有 a。用 _ 替换空格以提高可读性,相同的代码如下所示:

变量 a_ = 1;变量 a = 2;变量_a = 3; if(a_==1 && a== 2 &&_a==3) { console.log("为什么你好!") }

查看the validation on Mathias' variable name validator。如果这个奇怪的间距实际上包含在他们的问题中,我确信这是对这种答案的暗示。

不要这样做。严重地。

编辑:我注意到(尽管不允许启动变量)变量名称中也允许使用 Zero-width joinerZero-width non-joiner 字符 - 请参阅 Obfuscating JavaScript with zero-width characters - pros and cons?

这将如下所示:

变量 a = 1;变量 a‍= 2; //一个零宽度字符 var a‍‍= 3; //两个零宽度字符(或者你可以使用另一个) if(a==1&&a‍==2&&a‍==3) { console.log("Why hello there!") }


从原始问题中的奇数间距来看,我认为这正是面试问题所要寻找的答案——利用看起来像空格的非空格字符。好地方!
@Baracus 是 RonJohn 在他对凯文的回答的评论中注意到奇怪的间距,这让我想起了这种(可怕的)技术,所以我不能因为发现它而受到赞扬。不过,我有点惊讶没有人回答这个问题,因为几年前因为某处的一篇博客文章,它在我的工作中流传——我有点认为现在这是相当普遍的知识。
当然,这作为 standard loophole 被禁止,这也适用于采访。 [需要引用]
考虑到原来的间距,可能会更糟,即使用了变量var ᅠ2 = 3;所以有三个变量aᅠᅠ= 1, ᅠ2 = 3, a = 3a␣ = 1, ␣2 = 3, a = 3,所以(a␣==1 && a==␣2 && a==3))......
@AL-zami 在两个变量中有一个额外的字符,它在屏幕上显示为一个空格,但被解释为标识符的一部分,这意味着有三个单独的变量 - a、a 和 a - 额外的字符是韩文半角空间。
P
Pac0

有可能的!

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

这在 with 语句中使用 getter 来让 a 评估为三个不同的值。

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

更糟糕的是,这个技巧也适用于使用 ===

变量 i = 0; with({ get a() { return ++i; } }) { if (a !== a) console.log("是的,这是打印出来的。"); }


是的,我正在尝试同样的事情 :) 所以面试中的正确答案是,“这不会发生在 my 代码中,因为我从不使用 with。”
@Pointy - 而且,我在不允许 with 的严格模式下编程。
@Pointy 在接受的答案中,他们在没有 with 的情况下做了类似的事情,所以它可能会发生
@jorrit 没有人会使用 ==。并且 === 阻止接受的答案
@乔纳斯W。很多人仍然使用 ==,但我没有见过 with,因为......实际上从来没有在 JS 文档之外,上面写着“请不要使用它”。无论如何,一个不错的解决方案。
4
4 revs

没有 getter 或 valueOf 的示例:

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

这是因为 == 调用了 toString,而 toString 又为数组调用了 .join

另一种解决方案,使用 Symbol.toPrimitive,它是 toString/valueOf 的 ES6 等价物:

让我 = 0;让 a = { [Symbol.toPrimitive]: () => ++i }; console.log(a == 1 && a == 2 && a == 3);


without valueOf,嗯...它更间接但基本相同。
我真的很喜欢这个解决方案,因为除了对象自己的连接函数之外,您不会覆盖任何东西,而且它只是一个非常干净且易于阅读的 hack,它使逻辑评估为真。
老实说,我认为这是最好的答案。它没有什么特别之处,只是设置了一些值。即使有基本的 JS 知识也很容易理解。做得好。
这非常有意义,几乎感觉很有用。
我知道大多数答案都是关于滥用 toStringvalueOf 的,但这个答案让我完全措手不及。非常聪明,我不知道它确实会在内部调用.join,但它完全有道理。
P
Peter Mortensen

如果询问是否可能(不是必须),它可以要求“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("经过" + (i+1) + " 试验,终于成真!!!");休息; } } }


即使我知道其他解决方案,我也会故意给出这个答案,因为它回答了问题,但显然不是他们所追求的。玩愚蠢的游戏,赢得愚蠢的奖品。
但是如果它需要超过 1000 次试验呢?
@Piyin 如果它需要超过 1000 次试验,你就赢了!
我喜欢这个答案,因为将其发挥到极致表明,如果 cpu 的寄存器/缓存在程序运行时被足够的宇宙射线击中,或者如果有人故意执行电源故障以致故障分支的故障分支,这在任何语言中都是可能的if 条件实际上并没有跳转。
最低:1,最高:412。
K
Kos

当你没有正则表达式什么都做不了时:

var a = { r: /\d/g, valueOf: function(){ return this.r.exec(123)[0] } } if (a == 1 && a == 2 && a == 3) {控制台.log("!") }

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


@Abdillah 正则表达式对象会记住它匹配的最后一个索引,再次调用 exec 将从该索引开始搜索。 MDN 不是很清楚。
我明白了,所以 this.r 正则表达式对象记住了状态/索引。谢谢!
我建议将字符串传递给 exec,而不是要字符串化的整数。
使用正则表达式,现在你有 two problems
m
mehulmpt

如果变量 a 被 2 个网络工作者通过 SharedArrayBuffer 以及一些主脚本访问,这是可能的。可能性很低,但有可能当代码编译成机器码时,web worker 及时更新变量 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')
})

修饰符.js

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

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

https://i.stack.imgur.com/wjNp9.png

第二次尝试:

https://i.stack.imgur.com/wFrAt.png

正如我所说,机会很小,但只要有足够的时间,它就会达到条件。

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


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

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

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

这个答案通过定义一个 getter 来检索变量来滥用执行上下文中全局范围提供的隐式变量。


这假定 athis 的属性,但它似乎不是。如果 a 是一个局部变量(看起来像),那么这将不起作用。
@jfriend00 你的意思是如果你放置了 var a;某处?
是的。引用 a == 1 意味着 a 是某处的变量,而不是 this 的属性。虽然有一个像全局变量这样的奇怪地方,两者都可能为真,但通常,使用 var alet a 声明变量意味着没有 this 可以让您访问 a 作为代码假定的属性。因此,您的代码显然假设了一些奇怪的全局变量。例如,您的代码不能在 node.js 中运行,也不能在函数内部的严格模式下运行。您应该指定它工作的确切情况,并可能解释它为什么工作。否则,这是误导。
@jfriend00 很确定。不确定它是否会与其他已经回答的问题结合起来增加更多价值。会更新答案
问题是,这个“永远”是真的吗?答案是肯定的,这可能是其中一种情况:a 不是局部变量,而是在全局范围内使用递增的 getter 定义的。
C
Cerbrus

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

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

(() => { "use strict"; Object.defineProperty(this, "a", { "get": () => { Object.defineProperty(this, "a", { "get": () => { Object.defineProperty(this, "a", { "get": () => { return 3; } }); return 2; }, 可配置: true }); return 1; }, 可配置: true }); if (a == 1 && a == 2 && a == 3) { document.body.append("是的,有可能。"); } })();


请注意,使用 getter 的方法也适用于 ===,而不仅仅是 ==
此解决方案依赖于 this 作为箭头函数主体内的全局对象。
@Midnightas 我不会将任何其他答案归类为 "pyramid code"
请注意,这也适用于任意顺序,不是吗?比如,(a == 3 && a == 2 && a == 1)
A
Andrew Bone

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

函数 A() { 变量值 = 0; this.valueOf = function () { return ++value; }; } var a = 新的 A; if (a == 1 && a == 2 && a == 3) { console.log('bingo!'); }

编辑

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

类 A { 构造函数() { this.value = 0; this.valueOf(); } valueOf() { 返回 this.value++; }; } 让 a = 新的 A; if (a == 1 && a == 2 && a == 3) { console.log('bingo!'); }


刚开始function A() {value = 0;
valueOf 被覆盖,this method is usually called automatically by JavaScript behind the scenes, and not explicitly in code 所以当我们比较它的值时,它实际上增加了 a..
D
Draco18s no longer trusts SE

我没有看到这个答案已经发布,所以我也会把这个答案加入其中。这类似于具有半角韩文空间的 Jeff's answer

变量 a = 1; var a = 2;变量а = 3; if(a == 1 && a == 2 && а == 3) { console.log("为什么你好!") }

您可能会注意到与第二个略有差异,但第一个和第三个与肉眼相同。所有 3 个都是不同的字符:

a - 拉丁文小写 A
- 全角拉丁文小写 A
а - 西里尔小写 A

通用术语是“同形文字”:看起来相同的不同 unicode 字符。通常很难得到完全无法区分的三个,但在某些情况下你会很幸运。 A、Α、А 和 Ꭺ 会更好(分别为拉丁语 A、Greek AlphaCyrillic-ACherokee-A;不幸的是,希腊语和切诺基语的小写字母与拉丁语 a 差别太大:{ 5}、 等对上述代码段没有帮助)。

那里有一整类同形文字攻击,最常见的是假域名(例如。wikipediа.org(西里尔文)与 wikipedia.org(拉丁文)),但它也可以出现在代码中;通常被称为卑鄙(如评论中所述,[underhanded] 问题现在在 PPCG 上是题外话,但曾经是这类事情会出现的一种挑战)。我使用 this website 来查找用于此答案的同形文字。


"Slight discrepancy" 不是我所说的那样。
@hvd 完全取决于您的字体渲染。 This is what I see
@Jake是的,全宽拉丁小写A不是最大的同形文字(但大写字母变体很棒)。通常,尽管您只需要两个即可获得所需的效果。
您还可以使用 unicode 变体选择器 (U+FE00..U+FE0F)。这些都不是 aa︀ a︁ a︂。不再担心差异。
P
Peter Mortensen

对的,这是可能的! 😎

» JavaScript

如果‌=()=>!0;变量 a = 9; if‌(a==1 && a== 2 && a==3) { document.write("

是的,有可能!😎

") }

上面的代码是一个简短的版本(感谢@Forivin 在评论中的注释),下面的代码是原始的:

变量 a = 9; if‌(a==1 && a== 2 && a==3) { //console.log("是的,有可能!😎") document.write("

是的,有可能!😎< /h1>") } //------------------------------ -- 函数 if‌(){return true;}

如果您只是看到我的代码的顶部并运行它,您会说哇,怎么样?所以我认为说是的就足够了,对有人对你说:没有什么是不可能的技巧:我在 if 后面使用了一个隐藏字符来制作一个名称类似于 if 的函数。在 JavaScript 中我们不能覆盖关键字,所以我被迫使用这种方式。如果是假的,但在这种情况下它对你有用!

“ C#

我还写了一个 C# 版本(增加属性值技术):

static int _a;
public static int a => ++_a;

public static void Main()
{
    if(a==1 && a==2 && a==3)
    {
        Console.WriteLine("Yes, it is possible!😎");
    }
}

Live Demo


javascript 版本是真正的反人类罪,根据联合国公约,这样做的能力应该是非法的。我认为是时候清除世界上所有关于 javacript 的知识了。
函数声明可以更短。 if‌=()=>!0
你到底为什么使用document.write?无论其他答案如何,这都是不被录用的万无一失的方法。
@Cerbrus,谢谢你的来信。我首先用 console.log 写了我的答案,但我将其更改为 document.write。我真的总是在我的代码中使用 console.log,但在这里我只想在 StackOverflow 代码片段框中向用户显示一个文本。所以我想展示我的信息比 console.log 生成的信息更漂亮。单击我的答案和其他答案上的 Run Code Snippet 按钮。 SO Code Snippet 让我可以使用 html、JS 和 CSS,然后我想在我的答案中使用它并让它变得更好。我认为它没有任何负面影响,也没有使我的答案变大或完成。
@Clearer,如果联合国公约能够有效地改变世界,那么我们应该有一个比这更好的世界。我们需要的不仅仅是联合国的声明,直到那天我认为我们可以使用我的这个 Javascript 技巧;)

E
Eric Duminil

JavaScript

一个==一个+1

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

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

a = 100000000000000000 if (a == a+1 && a == a+2 && a == a+3){ console.log("精度损失!"); }

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

其他语言

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

红宝石

使用自定义 ==

class A
  def ==(o)
    true
  end
end

a = A.new

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

或增加的 a

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

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

Python

您可以为新类定义 ==

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!")

或者,如果您喜欢冒险,redefine the values of integers

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 控制台因上述代码而崩溃,因为可能在后台使用了 23。如果您使用不太常见的整数,它可以正常工作:

>>> 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 Integer cache

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.");
        }
    }
}

@cᴏʟᴅsᴘᴇᴇᴅ:Java、Javascript、potayto、potahto :) 已经有足够好的 JS 答案了。我只是认为展示如何用其他语言完成它会很有趣,并且可能会给 JS 开发人员一些想法。
@cᴏʟᴅsᴘᴇᴇᴅ:更新为 JS 示例。
为什么 Java 版本不能与 Integer a = 42 一起工作(或者不能)?据我了解自动装箱,Integer a = 42; a == 1 && a == 2 && a == 3 应该装箱所有的整数。或者这是否为比较而拆箱?
@CAD97:Integer == int 似乎导致拆箱。但是使用 Integer#equals(int) 会强制自动装箱,所以它可以工作。感谢您的评论!
@StephanBijzitter:请解释一下。据我所知,JS中只有Numbers,基本类似于double。它们看起来像整数,您可以像整数一样使用它们,但它们仍然不是整数。我不认为 n == n + 1 对于 Java/Python/C/Ruby/ 中的整数是真的...
S
Salman A

这是 @Jeff's answer* 的反转版本,其中隐藏字符(U+115F、U+1160 或 U+3164)用于创建看起来像 123 的变量。

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

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

变量 a = 1;变量 a‌ = 2;变量 a‍ = 3; console.log(a == 1 && a‌ == 2 && 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; a︁ = 2; a︀ == 1 && a︁ == 2; // true)。


M
MonkeyZeus

面试的第一条规则;永远不要说不可能。

无需隐藏角色诡计。

window.__defineGetter__( 'a', function(){ if( typeof i !== 'number' ){ // 在全局命名空间中定义 i,这样函数运行后不会丢失 i = 0; } return ++i ; }); if( a == 1 && a == 2 && a == 3 ){ console.log( '天哪,我们做了什么?' ); }


哎哟。 __defineGetter__ 实际上不是 js 语言的一部分,只是 defineProperty 的丑陋版本。 typeof 不是一个函数,这个未声明的 i 实在是太糟糕了。似乎仍然值得 40 票:/
@乔纳斯W。 41 票赞成 :-) 我知道每个 developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… 都弃用了 __defineGetter__,但它显然在我的 FireFox v 57.0.4 中执行,所以我选择展示它而不是 defineProperty(),因为遗留代码是真实的,不能被忽略。不管丑陋如何,以我所做的方式声明 i 是众所周知的/记录在案的行为。也许我只是在 PCG 心情¯\_(ツ)_/¯
F
Frank W. Zammetti

老实说,无论是否有办法评估它是否正确(正如其他人所表明的那样,有多种方法),作为一个进行过数百次采访的人来说,我正在寻找的答案将是类似于:

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

我想有些面试官会因为提出一个显然意味着非常棘手的问题而感到生气,但我不介意有意见的开发人员,特别是当他们可以用理性的想法支持它并且可以将我的问题与我的问题相吻合时关于自己的有意义的陈述。


这个问题(或所有面试问题)可能是为了测试候选人思考问题的意愿,尤其是那些“明显明显”的问题,比如这个问题。那些因为相信他们“知道”答案而拒绝思考的人不是一个好员工。
@Don Hatch 不,如果他们真诚地回答,我不会惩罚他们,特别是如果他们像其他人所展示的那样给出了正确的答案......但我会要求后续跟进,以尝试调查他们是否认为这是编写代码的好方法与否。知识渊博并能够提出“正确”的答案只是成为一名优秀开发人员的一部分。对于“专业”开发人员而言,更重要的是编写可理解和可维护的代码,通常由能力较弱的开发人员编写。过于聪明的开发人员与无能的 IME 开发人员一样糟糕。
这不能回答问题。
关于这个答案的可悲之处在于,一个 1rep 用户昨天回答了这个问题并得到了 2 票反对,导致他删除了这个问题。
@JohnColeman 问题询问代码如何评估为真。它没有首先询问面试官提出问题的原因。这个答案甚至没有试图解决被问到的问题,而是完全集中在“我会做什么”版本的尝试猜测面试官的目的是什么。如果这是问的问题,那就太宽泛了。因此,此答案不属于此处或站点上的任何位置。
D
Dirk Vollmar

如果你曾经遇到过这样的面试问题(或注意到代码中同样出乎意料的行为),请考虑一下什么样的事情可能会导致乍一看似乎不可能的行为:

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

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

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

副作用可能会变得非常讨厌,一个设计良好的程序应该没有不需要的副作用。

多线程和可变状态可能是有问题的。

不正确地进行字符编码和字符串处理会导致严重的错误。

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


然后,这个问题变得太宽泛了。不同的语言可以轻松地实现这一点。这个问题获得了很大的关注,因为它是一个特定于 JS 的问答,但这只是我的 2c。
原因是不同的 C# 和 javascript,所以这个答案是不合法的。
@Edwin:原因完全相同:Unicode 摆弄看起来相似的字形或空格字符、竞争条件或比较操作的副作用(后者在我的示例中显示)。
@cᴏʟᴅsᴘᴇᴇᴅ:有时从更广泛的角度看待事物有助于发现实际问题。
我希望这个答案可以以某种“元”方式标记到这个问题。看完上面的所有答案,我感觉JS有这么多漏洞,但你一口气总结了所有答案。在我看来,您这样做的方式使它成为一个出色的面试问题(如果删除了特定于语言的标签)。太棒了!
T
Théophile

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

const a = { n: [3,2,1], toString: function () { return anpop(); } } if(a == 1 && a == 2 && a == 3) { console.log('Yes'); }


D
Durga

好的,另一个使用生成器的 hack:

常量值 = 函数* () { 让 i = 0; while(true) 产生 ++i; }(); Object.defineProperty(this, 'a', { get() { return value.next().value; } }); if (a === 1 && a === 2 && a === 3) { console.log('yo!'); }


你说 hack,但我很确定这是生成器的用例...... :) (嗯,除了这依赖于 this 作为窗口对象)
P
Peter Mortensen

使用 Proxies

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

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

我们创建一个目标对象,{ i: 0 },其中 i 属性是我们的计数器 我们为目标对象创建一个 Proxy 并将其分配给 a 对于每个 a == 比较,a 的类型被强制转换为原始值 这种类型强制导致在内部调用 a[Symbol.toPrimitive]() 代理拦截使用“get handler”获取 a[Symbol.toPrimitive] 函数 Proxy 的“get handler”检查获取的属性是 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

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


不过,为此使用代理没有任何意义——在对象上以相同的方式定义 Symbol.toPrimitive 也同样有效。
T
Tim

实际上,在每种编程语言中,问题第一部分的答案都是“是”。例如,这是在 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;
}

我认为这在每一种编程语言中都是可能的。例如,并非所有语言都有预处理器。就此而言,并非所有语言都将 && 用于逻辑“与”。
我找到了一种适用于 PythonC++ 的方法,它使用运算符重载。
你可以在 Java 中通过使用反射和弄乱整数缓存来做到这一点。
不能用不支持该位置突变的语言来做到这一点,例如在 haskell 中没有可比的
问题是关于 JavaScript,而不是 C++。
P
Preda7or

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

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

我的想法是从 Number 对象类型方程的工作原理开始的。


P
Peter Mortensen

一个使用符号的 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


g
gafi

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

i=0,a={valueOf:()=>++i} if (a == 1 && a == 2 && a == 3) { console.log('Mind === Blown'); }

使用自定义 valueOf 创建一个虚拟对象,该对象会在每次调用时递增一个全局变量 i。 23个字符!


B
Ben Aubin

这个使用 defineProperty 具有很好的副作用,导致全局变量!

var _a = 1 Object.defineProperty(this, "a", { "get": () => { return _a++; }, 可配置:true }); console.log(a) console.log(a) console.log(a)


您可以在 a 上使用闭包:get: (a => () => ++a)(0), 不需要全局。
@NinaScholz 当然,但我们在这里谈论的是不好的做法 - 让我有这个:D
J
Jonathan Kuhl

通过在类声明中覆盖 valueOf,可以做到:

class Thing {
    constructor() {
        this.value = 1;
    }

    valueOf() {
        return this.value++;
    }
}

const a = new Thing();

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

发生的情况是在每个比较运算符中调用 valueOf。在第一个中,a 将等于 1,在第二个中,a 将等于 2,依此类推,因为每次调用 valueOfa 的值为递增。

因此,console.log 将触发并输出(无论如何在我的终端中)Thing: { value: 4},表明条件为真。


N
Nguyễn Văn Phong

正如我们已经知道的那样,loose equality operator (==) 的秘密将尝试将这两个值转换为一个通用类型。结果,将调用一些函数。

ToPrimitive(A) 尝试通过在 A 上调用不同的 A.toString 和 A.valueOf 方法序列,将其对象参数转换为原始值。

因此,使用整数中的 Symbol.toPrimitive.toString.valueOf 的其他答案也是如此。我会建议使用像这样的 Array.pop 数组的解决方案。

let a = { array: [3, 2, 1], toString: () => a.array.pop() }; if(a == 1 && a == 2 && a == 3) { console.log('Hello World!'); }

通过这种方式,我们可以处理这样的文本

let a = { array: ["World", "Hello"], toString: () => a.array.pop() }; if(a == "Hello" && a == "World") { console.log('Hello World!'); }