主持人注意:请不要编辑代码或删除此声明。空格模式可能是问题的一部分,因此不应不必要地对其进行篡改。如果您处于“空白无关紧要”的阵营中,则应该能够原样接受代码。
有可能用JavaScript (a== 1 && a ==2 && a==3)评估true吗?
这是一家大型科技公司提出的面试问题。它发生在两周前,但我仍在努力寻找答案。我知道我们从不在日常工作中编写此类代码,但我很好奇。
主持人注意:请不要编辑代码或删除此声明。空格模式可能是问题的一部分,因此不应不必要地对其进行篡改。如果您处于“空白无关紧要”的阵营中,则应该能够原样接受代码。
有可能用JavaScript (a== 1 && a ==2 && a==3)评估true吗?
这是一家大型科技公司提出的面试问题。它发生在两周前,但我仍在努力寻找答案。我知道我们从不在日常工作中编写此类代码,但我很好奇。
这使用了带有引起全局变量的副作用的defineProperty!
var _a = 1
Object.defineProperty(this, "a", {
"get": () => {
return _a++;
},
configurable: true
});
console.log(a)
console.log(a)
console.log(a)
使用Symbols的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.toPrimitiveJavaScript,则将使用您的函数返回的值。如果没有,JavaScript将调用valueOf。
如果您遇到这样的面试问题(或注意到您的代码中有一些同样意外的行为),请考虑一下什么样的事情可能导致看起来乍看之下是不可能的行为:
编码:在这种情况下,您正在查看的变量不是您认为的变量。如果您故意使用象形文字或空格字符来弄乱Unicode,以使变量名看起来像另一个变量,则可能会发生这种情况,但是编码问题也可能会偶然引入,例如,从Web复制和粘贴包含意外Unicode代码的代码时,点(例如,因为内容管理系统做了一些“自动格式化”,例如fl用Unicode“拉丁文小写本FL”(U + FB02)代替)。
竞争条件:可能发生竞争条件,即未按照开发人员期望的顺序执行代码的情况。竞态条件通常发生在多线程代码中,但是竞态条件不是必须要有多个线程–异步就足够了(不要感到困惑,异步并不意味着在后台使用了多个线程)。
请注意,因此JavaScript也不仅仅因为它是单线程而摆脱了竞争条件。有关简单的单线程但异步的示例,请参见此处。但是,在单个语句的上下文中,使用JavaScript很难达到竞争条件。
Web Worker的JavaScript有点不同,因为您可以有多个线程。@mehulmpt 使用Web worker向我们展示了一个很棒的概念证明。
副作用:相等比较操作的副作用(不必像此处的示例那样明显,副作用通常非常微妙)。
这些类型的问题可以出现在很多编程语言,不仅JavaScript,因此我们没有看到经典的一个JavaScript的WTFs这里1。
当然,面试问题和这里的样本看起来都是非常人为的。但它们很好地提醒您:
1举例来说,你可以找到在一个完全不同的编程语言(C#)的例子显示出副作用(一个明显的例子)在这里。
坦白说,不管是否有一种方法可以评估它是否为真(并且正如其他人所表明的那样,有多种方法),我作为一个进行过数百次采访的人所寻找的答案是类似于以下内容:
“好吧,也许是的,在某些奇怪的情况下,这些情况对我来说并不是立即显而易见的……但是,如果我在真实代码中遇到了这种情况,那么我将使用常见的调试技术来弄清楚它是如何以及为什么这样做的然后立即重构代码以避免这种情况……但更重要的是:我绝对不会一开始就编写该代码,因为那是卷积代码的定义,我将努力编写永不卷积的代码。”
我想有些面试官会冒犯显然被认为是一个非常棘手的问题的行为,但是我不介意有意见的开发人员,尤其是当他们可以用理性的思想支持并可以将我的问题与之吻合时关于自己的有意义的陈述。
这是一个倒置的版本@杰夫的回答 *其中隐藏字符(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)。
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,怎么办?
So I think it is enough to say Yes, it is possible to someone that said to you: Nothing is impossible
Trick: I used a hidden character after
ifto make a function that its name is similar toif. In JavaScript we can not override keywords so I forced to use this way. It is a fakeif, but it works for you in this case!
Also I wrote a C# version (with increase property value technic):
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!😎");
}
}
我没有看到这个答案已经发布,所以我也将这个答案加入了讨论范围。这类似于Jeff对半角Hangul空间的回答。
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
通用术语是“象形文字”:看起来相同的不同unicode字符。通常很难获得完全无法区分的三个,但是在某些情况下您会很幸运。A,Α,А和Ꭺ会更好地工作(分别为拉丁字母A,希腊字母Alpha,西里尔字母A和切诺基A;不幸的是,希腊字母和切诺基小写字母与拉丁字母差异太大a:α,,ꭺ所以对上述代码片段无济于事)。
这里有一整类的象形文字攻击,最常见的是假域名(例如wikipediа.org(西里尔字母)对wikipedia.org(拉丁字母)),但是它也可以显示在代码中。通常称为不熟练的人(如评论中提到的,[ 不熟练的人]问题现在在PPCG上已成为话题,但曾经是挑战的一种,这类事情会出现)。我使用该网站找到用于此答案的同形文字。
也可以使用一系列自我覆盖的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.");
}
})();
如果没有正则表达式就无法做任何事情:
var a = {
r: /\d/g,
valueOf: function(){
return this.r.exec(123)[0]
}
}
if (a == 1 && a == 2 && a == 3) {
console.log("!")
}
之所以起作用valueOf,是因为将Object与原始值(例如Number)进行比较时会调用自定义方法。主要技巧是a.valueOf每次都返回新值,因为它正在调用exec带有g标志的正则表达式,这会导致lastIndex每次找到匹配项时都会更新该正则表达式。因此,第一次this.r.lastIndex == 0,它匹配1并更新lastIndex:this.r.lastIndex == 1,因此下次正则表达式将匹配2,依此类推。
没有吸气剂或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);
如果询问是否可能(不是必须),则可以询问“ 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;
}
}
}
有可能的!
var i = 0;
with({
get a() {
return ++i;
}
}) {
if (a == 1 && a == 2 && a == 3)
console.log("wohoo");
}
这在with语句内部使用了一个吸气剂,可以a求出三个不同的值。
...这仍然不意味着应该在真实代码中使用...
更糟糕的是,此技巧也可以与配合使用===。
var i = 0;
with({
get a() {
return ++i;
}
}) {
if (a !== a)
console.log("yep, this is printed.");
}
通过覆盖
valueOf类声明,可以完成以下操作:What happens is that
valueOfis called in each comparison operator. On the first one,awill equal1, on the second,awill equal2, and so on and so forth, because each timevalueOfis called, the value ofais incremented.Therefore the console.log will fire and output (in my terminal anyways)
Thing: { value: 4}, indicating the conditional was true.