Can (a== 1 && a ==2 && a==3) ever evaluate to true?
技术背景
在常规编程思维中,一个变量 a
不能同时等于 1、2 和 3。但在 JavaScript 及其他一些编程语言中,利用语言的特性,如类型转换、特殊字符、代理等,是可以让 (a== 1 && a ==2 && a==3)
表达式评估为 true
的。这不仅是一个有趣的编程挑战,也能帮助开发者更深入地理解语言的底层机制。
实现步骤
JavaScript 实现方式
1. 自定义 toString
或 valueOf
方法
1 2 3 4 5 6 7 8 9 10
| const a = { i: 1, toString: function () { return a.i++; } }
if (a == 1 && a == 2 && a == 3) { console.log('Hello World!'); }
|
当使用松散相等运算符 ==
时,如果一边是对象,另一边是数字,引擎会尝试将对象转换为数字,先调用 valueOf
,若不可调用则调用 toString
。
2. 利用 Unicode 特殊字符创建不同变量
1 2 3 4 5 6
| var a = 1; var a = 2; var а = 3; if (a == 1 && a == 2 && а == 3) { console.log("Why hello there!"); }
|
这里的 a
、a
、а
是不同的 Unicode 字符,虽然看起来相似,但在代码中是不同的变量。
3. 使用 with
语句和 getter
1 2 3 4 5 6 7 8 9
| var i = 0; with ({ get a() { return ++i; } }) { if (a == 1 && a == 2 && a == 3) console.log("wohoo"); }
|
with
语句中的 getter 会让 a
每次返回不同的值。
4. 利用 Symbol.toPrimitive
1 2 3
| let i = 0; let a = { [Symbol.toPrimitive]: () => ++i }; console.log(a == 1 && a == 2 && a == 3);
|
Symbol.toPrimitive
是 ES6 中用于对象转换为原始值的方法。
5. 使用正则表达式
1 2 3 4 5 6 7 8 9 10
| var a = { r: /\d/g, valueOf: function () { return this.r.exec(123)[0] } }
if (a == 1 && a == 2 && a == 3) { console.log("!"); }
|
自定义 valueOf
方法,利用正则表达式的 g
标志,每次调用 exec
会返回不同的值。
6. 使用 Proxy
1 2 3 4
| var a = new Proxy({ i: 0 }, { get: (target, name) => name === Symbol.toPrimitive ? () => ++target.i : target[name], }); console.log(a == 1 && a == 2 && a == 3);
|
Proxy
可以拦截对象的操作,这里拦截了类型转换时的操作。
其他语言实现方式
Ruby
1 2 3 4 5 6 7 8 9 10
| class A def ==(o) true end end
a = A.new if a == 1 && a == 2 && a == 3 puts "Don't do this!" end
|
自定义 ==
方法,让对象与任何值比较都返回 true
。
Python
1 2 3 4 5 6 7 8
| 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!")
|
自定义类的 __eq__
方法,实现与任何值比较都为 True
。
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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
类的缓存值,实现条件判断为 true
。
核心代码
以下是几种常见的 JavaScript 实现的核心代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const a = { i: 1, toString: function () { return a.i++; } }
var i = 0; with ({ get a() { return ++i; } }) { if (a == 1 && a == 2 && a == 3) console.log("wohoo"); }
let i = 0; let a = { [Symbol.toPrimitive]: () => ++i }; console.log(a == 1 && a == 2 && a == 3);
|
最佳实践
虽然这些技巧可以让 (a== 1 && a ==2 && a==3)
评估为 true
,但在实际开发中应避免使用,因为它们会使代码难以理解和维护。在面试中遇到这类问题,应展示对语言特性的理解,并强调在实际项目中会使用清晰、可维护的代码。
常见问题
为什么这些方法在严格相等 ===
时不适用?
严格相等 ===
不会进行类型转换,而上述很多方法依赖于松散相等 ==
的类型转换机制,所以在严格相等时无法达到预期效果。
这些技巧会带来什么问题?
这些技巧会使代码的可读性和可维护性变差,增加调试难度,容易引入难以发现的错误。在团队协作开发中,可能会给其他开发者带来困扰。