it-swarm.cn

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

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

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

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

2376
Dimpu Aravind Buddha

如果您利用 ==如何工作 ,您可以简单地创建一个具有自定义toStringname __(或valueOfname__)函数的对象,该函数会在每次使用时更改它返回的内容,以使其满足所有三个条件。

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

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


这种方法的原因是由于使用了松散的等式运算符。当使用松散相等时,如果其中一个操作数的类型与另一个不同,则引擎将尝试将一个操作数转换为另一个。如果左边是一个对象,右边是一个数字,它会尝试通过首先调用valueOfname__(如果它是可调用的)将对象转换为数字,如果失败,它将调用toStringname__。我在这种情况下使用toStringname__只是因为它是我想到的,valueOfname__会更有意义。如果我从toStringname__返回一个字符串,那么引擎会尝试将字符串转换为给出相同最终结果的数字,但路径稍长。

3185
Kevin B

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

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之后的韩文,一个是之前的,另一个只是一个。为了便于阅读,用_替换空格,相同的代码如下所示:

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

1969
Jeff

IT IS可能!

var i = 0;

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

这使用with语句中的getter来让a计算为三个不同的值。

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

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

  var i = 0;

  with({
    get a() {
      return ++i;
    }
  }) {
    if (a !== a)
      console.log("yep, this is printed.");
  }

591
Jonas Wilms

没有getter或valueOf的示例:

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

这是有效的,因为==调用toString,它为数组调用.join

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

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

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

458
georg

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

259
mmmaaa

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

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

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

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

203
Kos

它可以在全局范围内使用以下内容完成。对于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来滥用执行上下文中的全局作用域提供的隐式变量。

186
jontro

如果变量a被2个Web工作人员通过SharedArrayBuffer以及一些主脚本访问,则可以这样做。可能性很低,但是当代码编译成机器代码时,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亿次迭代后发生:

enter image description here

第二次尝试:

enter image description here

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

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

182
mehulmpt

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

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

145
Patrick Dark

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

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

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

a - 拉丁文小写字母A.
- 全宽拉丁文小写字母A.
а - 西里尔文小写字母A.

这个通用术语是“同形文字”:看起来相同的不同的unicode字符。通常难以得到完全无法区分,但在某些情况下,你可以获得幸运。 A,Α,А和Ꭺ会更好地工作(拉丁语A, 希腊语AlphaCyrillic-ACherokee-A ;不幸的是希腊语和切诺基小写字母与拉丁文aα有太大的不同,所以对上面的代码片段没有帮助)。

那里有一整类的Homoglyph Attacks,最常见的是假域名(例如wikipediа.org(Cyrillic)vs wikipedia.org(Latin)),但它也可以出现在代码中;通常被称为卑鄙(在评论中提到, [不道德] 问题现在是关于 _ ppcg _ 的问题,但曾经是一种挑战,这些事情会显示出来向上)。我用 这个网站 找到用于这个答案的同形字符。

127
Draco18s

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

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

var a = new A;

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

_ edit _

使用ES6类它看起来像这样

class A {
  constructor() {
    this.value = 0;
    this.valueOf();
  }
  valueOf() {
    return this.value++;
  };
}

let a = new A;

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

123
Nina Scholz

JavaScript的

a == a +1

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

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

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

没错,这不是采访者所要求的(它不适用于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

蟒蛇

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

Java的

可以修改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.");
        }
    }
}
93
Eric Duminil

对的,这是可能的! ????

»JavaScript

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

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

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

var a = 9;

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

//--------------------------------------------

function if‌(){return true;}

如果你只看到我的代码的顶部并运行它你说WOW,怎么样?

所以我认为是的,有可能对某人说你:没有什么是不可能的就足够了

技巧:我在if之后使用了一个隐藏的字符来创建一个函数,它的名字与if相似。在JavaScript中,我们无法覆盖关键字,因此我不得不使用这种方式。这是假的if,但在这种情况下它适用于你!


» 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!????");
    }
}

现场演示

90
RAM

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

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

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

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

/****
var 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)。

78
Salman A

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

不需要隐藏的角色欺骗。

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?' );
}

72
MonkeyZeus

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

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

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

66
Frank W. Zammetti

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

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

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

41
Théophile

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

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

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

    请注意,因为它是单线程的,因此JavaScript也不会没有竞争条件。请参阅 here 以获取简单的单线程 - 但异步 - 示例。在单个语句的上下文中,竞争条件在JavaScript中很难实现。

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

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

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

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

  • 副作用可能变得非常讨厌,并且精心设计的程序应该没有不必要的副作用。
  • 多线程和可变状态可能是有问题的。
  • 不进行字符编码和字符串处理可能会导致令人讨厌的错误。

1 例如,你可以找到一个完全不同的编程语言(C#)的例子,它表现出副作用(一个明显的一个) here

37
Dirk Vollmar

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

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!');
}

31
BaggersIO

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

使用 代理

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. 代理拦截使用“获取处理程序”获取a[Symbol.toPrimitive]函数
  6. Proxy的“获取处理程序”检查获取的属性是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

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

27
IceCreamYou

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

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!');
}

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

26
Preda7or

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

23
Omar Alshaker

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

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

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

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

23
Gaafar

这个使用带有Nice副作用的defineProperty导致全局变量!

var _a = 1

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

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

11
Ben Aubin

通过在类声明中重写valueOfname__,可以完成:

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

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

const a = new Thing();

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

会发生什么是在每个比较运算符中调用valueOfname__。在第一个,aname__将等于1,在第二个,aname__将等于2,依此类推,因为每次调用valueOfname__时,aname__的值都会递增。

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

0
Jonathan Kuhl