主持人备注:请拒绝修改代码或删除此通知的冲动。空白模式可能是问题的一部分,因此不应该被不必要地篡改。如果您在“空白无关紧要”的阵营中,您应该能够按原样接受该代码。
(a== 1 && a ==2 && a==3)
是否有可能在JavaScript中评估为true
?
这是一家大型科技公司提出的面试问题。它发生在两周前,但我仍在努力寻找答案。我知道我们从来没有在日常工作中写过这样的代码,但我很好奇。
答案 0 :(得分:3242)
如果您利用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
(如果它是可调用的)将对象转换为数字,如果失败,它将调用{{1} }。在这种情况下,我使用了toString
只是因为它是我想到的,toString
会更有意义。如果我改为从valueOf
返回一个字符串,那么引擎会尝试将字符串转换为给出相同最终结果的数字,但路径稍长。
答案 1 :(得分:2015)
我无法抗拒 - 其他答案无疑是正确的,但你真的无法超越以下代码:
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!")
}
结帐the validation on Mathias' variable name validator。如果这个奇怪的间距实际上包含在他们的问题中,我确信这是这种答案的暗示。
不要这样做。严重。
编辑:我注意到(尽管不允许启动变量)变量名中也允许使用Zero-width joiner和Zero-width non-joiner字符 - 请参阅Obfuscating JavaScript with zero-width characters - pros and cons?。
这将如下所示:
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!")
}
答案 2 :(得分:604)
这是可能的!
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.");
}

答案 3 :(得分:486)
没有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 i = 0;
let a = { [Symbol.toPrimitive]: () => ++i };
console.log(a == 1 && a == 2 && a == 3);
答案 4 :(得分:264)
如果询问是否可能(不是必须),它可以询问" 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;
}
}
}
&#13;
答案 5 :(得分:205)
如果没有正则表达式就无法做任何事情:
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
并更新lastIndex
:this.r.lastIndex == 1
,因此下次正则表达式将匹配2
,依此类推。
答案 6 :(得分:188)
可以在全局范围内使用以下内容来完成。对于nodejs
,请在下面的代码中使用global
而不是window
。
var val = 0;
Object.defineProperty(window, 'a', {
get: function() {
return ++val;
}
});
if (a == 1 && a == 2 && a == 3) {
console.log('yay');
}
&#13;
此答案通过定义用于检索变量的getter来滥用执行上下文中全局范围提供的隐式变量。
答案 7 :(得分:186)
如果变量a
被2个Web工作人员通过SharedArrayBuffer以及一些主脚本访问,则可以这样做。可能性很低,但是当代码编译为机器代码时,Web工作人员可能会及时更新变量a
,因此条件a==1
,a==2
和{{ 1}}感到满意。
这可以是由web worker和JavaScript中的SharedArrayBuffer提供的多线程环境中的竞争条件示例。
以上是基本实现:
main.js
a==3
worker.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)
modifier.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')
})
在我的MacBook Air上,它在首次尝试大约100亿次迭代后发生:
第二次尝试:
正如我所说,机会很低,但如果有足够的时间,它就会达到最佳状态。
提示:如果您的系统需要太长时间。仅尝试addEventListener('message' , ({data}) => {
setInterval( () => {
new Uint8Array(data)[0] = Math.floor(Math.random()*3) + 1
})
})
并将a == 1 && a == 2
更改为Math.random()*3
。添加越来越多的列表会降低击中的可能性。
答案 8 :(得分:145)
使用一系列自覆盖吸气剂也可以实现这一点:
(这类似于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.");
}
})();
答案 9 :(得分:128)
我没有看到这个答案已经发布,所以我也会把这个问题扔进去。这与具有半宽Hangul空间的Jeff's answer类似。
var a = 1;
var a = 2;
var а = 3;
if(a == 1 && a == 2 && а == 3) {
console.log("Why hello there!")
}
您可能会注意到与第二个略有差异,但第一个和第三个与肉眼相同。所有3个都是截然不同的字符:
a
- 拉丁文小写A
a
- 全宽拉丁文小写A
а
- 西里尔文小写字母A
这个术语的通用术语是“homoglyphs”:看起来相同的不同unicode字符。通常难以获得完全无法区分的三个,但在某些情况下,您可以获得幸运。 A,Α,А和Ꭺ分别会更好(拉丁语A,Greek Alpha,Cyrillic-A和Cherokee-A;不幸的是,希腊语和切诺基小写字母与拉丁语a
:α
,ꭺ
等对上述代码段没有帮助。)
那里有一整类的Homoglyph Attacks,最常见的是假域名(例如wikipediа.org
(西里尔语)vs wikipedia.org
(拉丁语)),但它也可以在代码中出现;通常被称为不公平(如评论中所述,[underhanded]问题现在在PPCG上是偏离主题的,但过去常常出现这种类型的挑战。我用this website找到了用于这个答案的同形字。
答案 10 :(得分:125)
或者,你可以使用一个类和一个检查实例。
function A() {
var value = 0;
this.valueOf = function () { return ++value; };
}
var a = new A;
if (a == 1 && a == 2 && a == 3) {
console.log('bingo!');
}
修改强>
使用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!');
}
答案 11 :(得分:100)
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#版本(增加属性值技术):
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 强>
答案 12 :(得分:95)
在JavaScript中,没有integers但只有Number
s,它们被实现为双精度浮点数。
这意味着如果数字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 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.");
}
}
}
答案 13 :(得分:79)
这是@Jeff's answer *的反转版本,其中隐藏字符(U + 115F,U + 1160或U + 3164)用于创建看起来像1
,{{1}的变量}和2
。
3
&#13;
*通过使用零宽度非连接器(U + 200C)和零宽度连接器(U + 200D)可以简化答案。这两个字符都允许在标识符内,但不允许在开头:
var a = 1;
var ᅠ1 = a;
var ᅠ2 = a;
var ᅠ3 = a;
console.log( a ==ᅠ1 && a ==ᅠ2 && a ==ᅠ3 );
&#13;
其他技巧可能使用相同的想法,例如:通过使用Unicode变体选择器来创建看起来完全相同的变量(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);
****/
)。
答案 14 :(得分:73)
规则第一的访谈;永远不要说不可能。
不需要隐藏的角色技巧。
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?' );
}
&#13;
答案 15 :(得分:68)
老实说,是否有办法让它评价为真或不(正如其他人所表明的那样,有多种方式),我正在寻找的答案,作为一个有数百人的人说话采访的方式将是:
&#34;好吧,也许是在一些奇怪的情况下,对我来说并不是很明显...但如果我在实际代码中遇到这个,那么我会使用常见的调试技术来弄清楚如何和为什么它正在做它正在做的事情,然后立即重构代码以避免这种情况......但更重要的是:我绝对不会首先编写代码,因为这是复杂代码的定义,我努力永远不要写错综复杂的代码&#34;。
我猜一些采访者会冒犯一个明显意味着一个非常棘手的问题,但我不介意有意见的开发人员,特别是当他们能够用合理的思想支持它时可以将我的问题与自己的有意义的陈述相吻合。
答案 16 :(得分:41)
这是另一个变体,使用数组弹出你想要的任何值。
const a = {
n: [3,2,1],
toString: function () {
return a.n.pop();
}
}
if(a == 1 && a == 2 && a == 3) {
console.log('Yes');
}
&#13;
答案 17 :(得分:41)
如果您遇到过这样的面试问题(或者在代码中注意到一些同样出乎意料的行为),请考虑哪些事情可能导致乍一看似乎不可能的行为:
编码:在这种情况下,您正在查看的变量不是您认为的变量。如果您故意使用homoglyphs或space characters使用Unicode来使变量的名称看起来像另一个变量,则可能会发生这种情况,但编码问题也可能会偶然引入,例如:复制时从Web中粘贴包含意外Unicode代码点的代码(例如,因为内容管理系统执行了一些&#34;自动格式化&#34;例如用Unicode替换fl
&#39; LATIN SMALL LIGATURE FL&#39 ;(U + FB02))。
竞争条件:可能会出现race-condition,即代码未按开发人员预期的顺序执行的情况。竞争条件经常发生在多线程代码中,但多线程并不是竞争条件的必要条件 - 异步性就足够了(并且不要混淆,async does not mean multiple threads are used under the hood)。
请注意,因为它是单线程的,因此JavaScript也不会没有竞争条件。有关简单的单线程 - 但异步 - 示例,请参阅here。在单个语句的上下文中,竞争条件在JavaScript中很难被触及。
与Web worker的JavaScript有点不同,因为您可以拥有多个线程。 @mehulmpt向我们展示了一个很棒的proof-of-concept using web workers。
副作用:相等比较操作的副作用(不必像此处的示例那样明显,通常副作用非常大微妙)。
这些问题可能出现在许多编程语言中,而不仅仅是JavaScript,所以我们没有看到其中一个经典的JavaScript WTFs 1 。
当然,面试问题和这里的样本都看起来非常人为。但他们提醒我们:
1 例如,你可以找到一个完全不同的编程语言(C#)的例子,它表现出副作用(一个明显的)here。
答案 18 :(得分:31)
好的,发电机的另一个黑客:
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!');
}
&#13;
答案 19 :(得分:27)
实际上,问题的第一部分的答案在每种编程语言中都是“是”。例如,这是在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;
}
答案 20 :(得分:27)
使用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);
代理基本上假装是目标对象(第一个参数),但拦截对目标对象的操作(在本例中为“get property”操作),这样就有机会做除默认对象行为以外的事情。在这种情况下,当a
强制其类型时,==
会调用“get property”操作,以便将其与每个数字进行比较。发生这种情况:
{ i: 0 }
,其中i
属性是我们的计数器a
a ==
比较,a
的类型被强制转换为原始值a[Symbol.toPrimitive]()
a[Symbol.toPrimitive]
函数
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
与大多数其他答案一样,这仅适用于松散的等式检查(==
),因为严格的相等性检查(===
)不会执行代理可以拦截的类型强制。< / p>
答案 21 :(得分:26)
相同但不同,但仍然相同(可以&#34;测试&#34;多次):
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!');
}
&#13;
我的想法始于数字对象类型方程的工作原理。
答案 22 :(得分:24)
使用符号的ECMAScript 6答案:
const a = {value: 1};
a[Symbol.toPrimitive] = function() { return this.value++ };
console.log((a == 1 && a == 2 && a == 3));
由于==
的使用,JavaScript应该强制a
强制接近第二个操作数(1
,2
,3
。 )。但是在JavaScript尝试自己进行强制攻击之前,它会尝试调用Symbol.toPrimitive
。如果您提供Symbol.toPrimitive
,JavaScript将使用您的函数返回的值。如果没有,JavaScript会调用valueOf
。
答案 23 :(得分:23)
我认为这是实现它的最小代码:
i=0,a={valueOf:()=>++i}
if (a == 1 && a == 2 && a == 3) {
console.log('Mind === Blown');
}
&#13;
使用自定义valueOf
创建一个虚拟对象,在每次调用时递增全局变量i
。 23个字符!
答案 24 :(得分:12)
这个使用了带有引起全局变量的好副作用的defineProperty!
var _a = 1
Object.defineProperty(this, "a", {
"get": () => {
return _a++;
},
configurable: true
});
console.log(a)
console.log(a)
console.log(a)
答案 25 :(得分:0)
通过在类声明中覆盖 $counts = [
'notChecked' => 0,
'published' => 0,
'total' => 0
];
$this->reviewPhotosRepository->getByHotelId($hotel_id)->map(function($photo) use (&$counts) {
$photo->checked ?: $counts['notChecked']++;
$photo->published ?: $counts['published']++;
$counts['total']++;
});
return $counts;
,可以做到:
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
被调用时,valueOf
的值将递增。
因此,console.log将触发并输出(无论如何在我的终端中)a
,指示条件为真。
答案 26 :(得分:-1)
正如我们已经知道的那样,loose equality operator (==) 的秘密会尝试将两个值转换为通用类型。因此,将调用一些函数。
<块引用>ToPrimitive(A)
尝试将其对象参数转换为原始类型
值,通过调用 A.toString
和 A.valueOf
的不同序列
A 上的方法。
其他答案使用 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!');
}