提问人:Dimpu Aravind Buddha 提问时间:1/16/2018 最后编辑:Brad LarsonDimpu Aravind Buddha 更新时间:7/22/2023 访问量:417473
(a== 1 & & a ==2 && a==3) 可以计算为真吗?
Can (a== 1 && a ==2 && a==3) ever evaluate to true?
问:
主持人注:请抵制编辑代码或删除此通知的冲动。空格模式可能是问题的一部分,因此不应被不必要地篡改。如果你在“空格无关紧要”的阵营中,你应该能够按原样接受代码。
是否有可能在 JavaScript 中进行评估?(a== 1 && a ==2 && a==3)
true
这是一家大型科技公司提出的面试问题。它发生在两周前,但我仍在努力寻找答案。我知道我们在日常工作中从不写这样的代码,但我很好奇。
答:
这是可能的!
var i = 0;
with({
get a() {
return ++i;
}
}) {
if (a == 1 && a == 2 && a == 3)
console.log("wohoo");
}
这使用语句中的 getter 来计算三个不同的值。with
a
...这仍然不意味着应该在实际代码中使用......
更糟糕的是,这个技巧也适用于使用 .===
var i = 0;
with({
get a() {
return ++i;
}
}) {
if (a !== a)
console.log("yep, this is printed.");
}
评论
with
with
with
==
===
==
with
如果您利用 ==
的工作原理,您可以简单地创建一个带有自定义(或)函数的对象,该函数在每次使用时都会更改它返回的内容,以便它满足所有三个条件。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()
i
可以在全局范围内使用以下方法完成此操作。用于代替在下面的代码中。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 来检索变量,从而滥用了执行上下文中全局作用域提供的隐式变量。
评论
a
this
a
a == 1
a
this
var a
let a
this
a
a
我无法抗拒 - 其他答案无疑是正确的,但你真的无法跳过以下代码:
var aᅠ = 1;
var a = 2;
var ᅠa = 3;
if(aᅠ==1 && a== 2 &&ᅠa==3) {
console.log("Why hello there!")
}
请注意语句中奇怪的间距(我从您的问题中复制的)。它是半角韩文(对于那些不熟悉的人来说是韩语),它是一个 Unicode 空格字符,不会被 ECMA 脚本解释为空格字符 - 这意味着它是标识符的有效字符。因此,有三个完全不同的变量,一个是 a 之后的韩文,一个是 a 之前的韩文,最后一个是 a。为了便于阅读,将空格替换为相同的代码,如下所示:if
_
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!")
}
评论
var ᅠ2 = 3
aᅠᅠ= 1, ᅠ2 = 3, a = 3
a␣ = 1, ␣2 = 3, a = 3
(a␣==1 && a==␣2 && a==3)
如果询问是否可能(不是必须的),它可以要求“a”返回一个随机数。如果它按顺序生成 1、2 和 3,则为 true。
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;
}
}
}
评论
这也可以使用一系列自覆盖 getter 来实现:
(这类似于 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.");
}
})();
评论
===
==
this
(a == 3 && a == 2 && a == 1)
或者,您可以为它使用一个类,为检查使用一个实例。
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!');
}
评论
function A() {value = 0;
valueOf
被覆盖,所以当我们比较这个值时,它实际上会增加一个。this method is usually called automatically by JavaScript behind the scenes, and not explicitly in code
面试的第一条规则;永远不要说不可能。
无需隐藏的角色诡计。
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 ){
console.log( 'Oh dear, what have we done?' );
}
评论
__defineGetter__
defineProperty
typeof
i
__defineGetter__
defineProperty()
i
我没有看到这个答案已经发布,所以我也会把这个答案扔进去。这类似于 Jeff 对半宽韩文空间的回答。
var a = 1;
var a = 2;
var а = 3;
if(a == 1 && a == 2 && а == 3) {
console.log("Why hello there!")
}
您可能会注意到与第二个略有不同,但第一个和第三个与肉眼相同。这 3 个都是不同的角色:
a
- 拉丁文小写字母 A - 全宽拉丁文小写字母 A - 西里尔文小写字母 Aa
а
这方面的通用术语是“同形文字”:看起来相同的不同 Unicode 字符。通常很难获得三个完全无法区分的,但在某些情况下,您可能会很幸运。A、Α、А 和 Ꭺ 会更好用(分别是拉丁文 A、希腊文 Alpha、西里尔文 A 和切诺基文 A;不幸的是,希腊文和切诺基文的小写字母与拉丁文 :,,因此对上述片段没有帮助)。a
α
ꭺ
有一整类的同形文字攻击,最常见的是假域名(例如。 (西里尔文)与(拉丁文)),但它也可以出现在代码中;通常被称为卑鄙(正如评论中提到的,[卑鄙的]问题现在在PPCG上是偏离主题的,但曾经是一种挑战,这些事情会出现)。我使用这个网站来查找用于此答案的同形文字。wikipediа.org
wikipedia.org
评论
a
a︀
a︁
a︂
这个使用 defineProperty,有一个很好的副作用导致全局变量!
var _a = 1
Object.defineProperty(this, "a", {
"get": () => {
return _a++;
},
configurable: true
});
console.log(a)
console.log(a)
console.log(a)
评论
a
get: (a => () => ++a)(0),
当你没有正则表达式就什么都做不了的时候:
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
方法。主要诀窍是每次都返回新值,因为它调用带有标志的正则表达式,这会导致每次找到匹配项时更新该正则表达式的 lastIndex
。所以第一次,它匹配并更新:,所以下次正则表达式会匹配,依此类推。a.valueOf
exec
g
this.r.lastIndex == 0
1
lastIndex
this.r.lastIndex == 1
2
评论
this.r
exec
实际上,在每种编程语言中,问题第一部分的答案都是“是”。例如,这是在 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;
}
评论
&&
这是另一个变体,使用数组弹出您想要的任何值。
const a = {
n: [3,2,1],
toString: function () {
return a.n.pop();
}
}
if(a == 1 && a == 2 && a == 3) {
console.log('Yes');
}
好的,另一个带有生成器的黑客:
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!');
}
评论
this
老实说,无论有没有办法评估它是否真实(正如其他人所表明的那样,有多种方法),我正在寻找的答案,作为一个进行过数百次采访的人,将是大致如下:
“嗯,也许是的,在一些奇怪的情况下,这些情况对我来说并不明显......但是如果我在实际代码中遇到这种情况,那么我会使用常见的调试技术来弄清楚它如何以及为什么做它正在做的事情,然后立即重构代码以避免这种情况......但更重要的是:我绝对不会首先编写该代码,因为这是复杂代码的定义,我努力从不编写复杂代码。
我猜有些面试官会因为提出一个显然非常棘手的问题而感到生气,但我不介意那些有意见的开发人员,尤其是当他们能够用理性的思考来支持它,并能将我的问题与关于他们自己的有意义的陈述相吻合时。
评论
如果变量被 2 个 Web 工作者通过 SharedArrayBuffer 以及一些主脚本访问,这是可能的。这种可能性很低,但当代码被编译为机器码时,Web Worker 可能会及时更新变量,从而满足条件。a
a
a==1
a==2
a==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)
工作.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
评论
JavaScript的
一个 == 一个 +1
在 JavaScript 中,没有整数,只有 s,它们被实现为双精度浮点数。Number
这意味着,如果一个数字足够大,它可以被认为是等于四个连续的整数:a
a = 100000000000000000
if (a == a+1 && a == a+2 && a == a+3){
console.log("Precision loss!");
}
诚然,这并不完全是面试官所问的(它不适用于),但它不涉及任何隐藏函数或运算符重载的技巧。a=0
其他语言
作为参考,Ruby 和 Python 中有解决方案。稍作修改,在 Java 中也可以。a==1 && a==2 && a==3
红宝石
自定义 :==
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!")
或者,如果您喜欢冒险,请重新定义整数的值:
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 控制台因上述代码而崩溃,因为 or 可能在后台使用。如果您使用不太常见的整数,它可以正常工作: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 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.");
}
}
}
评论
Integer a = 42
Integer a = 42; a == 1 && a == 2 && a == 3
Integer == int
Integer#equals(int)
Numbers
double
n == n + 1
没有 getter 或 valueOf 的示例:
a = [1,2,3];
a.join = a.shift;
console.log(a == 1 && a == 2 && a == 3);
这之所以有效,是因为调用调用数组。==
toString
.join
另一种解决方案,使用是 ES6 等价物:Symbol.toPrimitive
toString/valueOf
let i = 0;
let a = { [Symbol.toPrimitive]: () => ++i };
console.log(a == 1 && a == 2 && a == 3);
评论
without valueOf
井。。。它更间接,但基本上是一回事。
toString
valueOf
.join
如果你遇到这样的面试问题(或者注意到你的代码中有一些同样出乎意料的行为),想想什么样的事情可能会导致乍一看不可能的行为:
编码:在这种情况下,您正在查看的变量不是您认为的变量。如果您故意使用同形字形或空格字符来弄乱 Unicode,以使变量的名称看起来像另一个变量,则可能会发生这种情况,但编码问题也可能意外引入,例如,当从 Web 复制和粘贴包含意外 Unicode 代码点的代码时(例如,因为内容管理系统进行了一些“自动格式化”,例如替换为 Unicode “LATIN SMALL LIGATURE FL” (U+FB02))。
fl
争用条件:可能会发生争用条件,即代码未按开发人员预期的顺序执行的情况。竞争条件通常发生在多线程代码中,但多线程并不是竞争条件成为可能的必要条件 - 异步性就足够了(不要混淆,异步并不意味着在后台使用多个线程)。
请注意,因此 JavaScript 也不能仅仅因为它是单线程的而不受竞争条件的影响。请参阅此处,了解一个简单的单线程(但异步)示例。然而,在单个语句的上下文中,竞争条件在 JavaScript 中很难达到。
带有 Web Worker 的 JavaScript 有点不同,因为您可以有多个线程。@mehulmpt向我们展示了使用 Web Worker 的出色概念验证。
副作用:相等比较操作的副作用(不必像此处的示例那样明显,通常副作用非常微妙)。
这类问题可能出现在许多编程语言中,而不仅仅是 JavaScript,因此我们在这里没有看到经典的 JavaScript WTF 之一1.
当然,面试问题和这里的样本看起来都非常做作。但它们很好地提醒了我们:
- 副作用可能会变得非常令人讨厌,并且精心设计的程序应该没有不必要的副作用。
- 多线程和可变状态可能会有问题。
- 不正确地进行字符编码和字符串处理可能会导致令人讨厌的错误。
1 例如,您可以在此处找到一个完全不同的编程语言 (C#) 的示例,该示例表现出副作用(明显的副作用)。
评论
相同,但不同,但仍然相同(可以多次“测试”):
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!');
}
我的想法是从数字对象类型方程的工作原理开始的。
使用 Symbols 的 ECMAScript 6 答案:
const a = {value: 1};
a[Symbol.toPrimitive] = function() { return this.value++ };
console.log((a == 1 && a == 2 && a == 3));
由于使用的原因,JavaScript 应该强制转换为接近第二个操作数(在本例中为 、 )的东西。但是在 JavaScript 尝试自行计算强制之前,它会尝试调用 Symbol.toPrimitive
。如果提供 JavaScript,则使用函数返回的值。如果没有,JavaScript 将调用 valueOf
。==
a
1
2
3
Symbol.toPrimitive
这是 @Jeff 答案* 的倒置版本,其中隐藏字符(U+115F、U+1160 或 U+3164)用于创建类似于 和 的变量。1
2
3
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
评论
使用代理:
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 属性”操作),以便有机会执行默认对象行为以外的操作。在这种情况下,当强制其类型以将其与每个数字进行比较时,将调用“get property”操作。发生以下情况:a
==
- 我们创建一个目标对象,其中属性是我们的计数器
{ i: 0 }
i
- 我们为目标对象创建一个代理,并将其分配给
a
- 对于每次比较,的类型都被强制转换为基元值
a ==
a
- 这种类型的强制会导致内部调用
a[Symbol.toPrimitive]()
- 代理使用“get handler”拦截获取函数
a[Symbol.toPrimitive]
- 代理的“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
是的,这是可能的!😎
» 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;}
如果你只是看到我的代码的顶部并运行它,你会说哇,怎么样?
所以我认为说“是”就足够了,有人说 你:没有什么是不可能的
诀窍:我使用了一个隐藏字符来制作一个函数,其名称类似于 。在 JavaScript 中,我们不能覆盖关键字,所以我被迫使用这种方式。这是假的,但在这种情况下它对你有用!
if
if
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!😎");
}
}
评论
if=()=>!0
document.write
console.log
console.log
console.log
Run Code Snippet
我认为这是实现它的最小代码:
i=0,a={valueOf:()=>++i}
if (a == 1 && a == 2 && a == 3) {
console.log('Mind === Blown');
}
使用自定义创建虚拟对象,该自定义值在每次调用时递增全局变量。23个字符!valueOf
i
通过在类声明中重写,可以完成以下操作: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
a
因此,控制台.log将触发并输出(无论如何在我的终端中),表明条件为真。Thing: { value: 4}
正如我们已经知道的那样,松散相等运算符 (==) 的秘密将尝试将这两个值转换为通用类型。因此,将调用某些函数。
ToPrimitive(A)
尝试将其对象参数转换为基元 value,通过在 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!');
}
是的,您可以这样做,请参阅以下 JavaScript 代码:
let a = 0 // Create a variable and give it a value
if( a !== 1 && a !== 2 && a !== 3 )
{
console.log("true")
}
解决方案说明:
简单地说,我们添加不等号 在 == 符号之前,以便我们告诉语言这些值是 不等于变量中的值
如果我们使用 JavaScript 将对象转换为原始值的属性及其 getter 函数,这是可能的。
const a = {
value: 0,
valueOf: function() {
return this.value += 1;
}
}
if (a == 1 && a == 2 && a == 3) {
console.log('it can')
}
评论
==
===