引言:引爆 Stack Overflow 的问题
\n\n想象一下这个场景:你在面试,面试官带着狡黠的微笑问道:“表达式 a == 1 && a == 2 && a == 3 有没有可能返回 true?”。第一反应是——“当然不可能!一个变量不可能同时等于三个不同的数字。”
然而,如果你对 JavaScript 有更深入的了解,你就会知道:这门语言中可能发生最意想不到的事情。这个问题在 Stack Overflow 上获得了数千次浏览,并成为检验对 JS 中 类型转换、getter 和 对象包装器 理解的经典问题。
\n\n在本文中,我们不仅要证明这是可能的,还将分析 5 种不同的方法,让 (a==1 && a==2 && a==3) 返回 true。你将了解 非严格比较 (==) 的工作原理,它与严格比较 (===) 的区别,以及如何控制它。
本文既适合希望更深入理解 JavaScript 的初学者,也适合准备面试的经验丰富的开发者。开始吧!
\n\n方法 #1:valueOf 的魔法(最流行)
\n\n最优雅且最常被引用的方法是使用一个 自定义对象,并重写其 valueOf 方法。在 JavaScript 中,当你通过 == 将对象与原始值进行比较时,引擎会自动调用 valueOf()(或 toString())将对象转换为原始值。
我们将创建一个计数器对象,每次调用 valueOf 时,其值都会增加 1。
const a = {\n value: 1,\n valueOf() {\n return this.value++;\n }\n};\n\nconsole.log(a == 1 && a == 2 && a == 3); // true\n\n工作原理:
\n- \n
- 在第一次比较
a == 1时,调用valueOf(),返回 1,并且value增加到 2。 \n - 在第二次比较
a == 2时,再次调用valueOf(),返回 2,value变为 3。 \n - 第三次比较
a == 3成功完成链条。 \n
这是 类型强制转换 在行动中的完美示例。正是这种机制使 JavaScript 同时成为一种灵活且危险的语言。
\n\n方法 #2:toString——valueOf 的孪生兄弟
\n\n如果你重写 toString 而不是 valueOf,结果是一样的。在非严格比较中,JavaScript 可以使用这两种方法中的任何一种来转换对象。
const a = {\n value: 1,\n toString() {\n return this.value++;\n }\n};\n\nconsole.log(a == 1 && a == 2 && a == 3); // true\n\n细微的区别在于:对于数值比较,valueOf 具有优先级;而对于字符串比较,toString 具有优先级。但在我们的例子中,两者都非常适用。
方法 #3:全局作用域中的 Getter
\n\n许多人甚至没有意识到,JavaScript 中的 全局变量 实际上是全局对象(浏览器中的 window,Node.js 中的 global)的属性。而属性可以通过 getter 来定义。
我们可以直接在全局对象上为变量 a 定义一个 getter:
let value = 1;\nObject.defineProperty(window, 'a', {\n get() {\n return value++;\n }\n});\n\nconsole.log(a == 1 && a == 2 && a == 3); // true\n\n这个方法特别有趣,因为 a 表现得像一个普通变量,尽管它实际上是一个带有 getter 的属性。每次你访问 a 时,getter 都会返回一个新值。