专栏名称: SegmentFault思否
SegmentFault (www.sf.gg)开发者社区,是中国年轻开发者喜爱的极客社区,我们为开发者提供最纯粹的技术交流和分享平台。
目录
相关文章推荐
程序员小灰  ·  3个令人惊艳的DeepSeek项目,诞生了! ·  9 小时前  
程序猿  ·  “我真的受够了Ubuntu!” ·  昨天  
程序员小灰  ·  DeepSeek做AI代写,彻底爆了! ·  3 天前  
待字闺中  ·  DeepSeek 爆火带来的大变化 ·  1 周前  
51好读  ›  专栏  ›  SegmentFault思否

如何让 (a===1&&a===2&&a===3) 的值为 true?

SegmentFault思否  · 公众号  · 程序员  · 2020-03-11 11:47

正文

本文转载于 SegmentFault 社区

作者:Eno_Yao



当我第一次看到这一题目的时候,我是比较震惊的,分析了下很不合我们编程的常理,并认为不大可能,变量 a 要在同一情况下要同时等于 1,2 和 3 这三个值,这是天方夜谭吧,不亚于哥德巴赫 1+1=1 的猜想吧,不过一切皆有可能,出于好奇心,想了许久之后我还是决定尝试解决的办法。


我的思路来源于更早前遇到的另外一题相似的面试题:

// 设置一个函数输出一下的值
f(1) = 1;
f(1)(2) = 2;
f(1)(2)(3) = 6;

当时的解决办法是使用 toString 或者 valueOf 实现的,那我们先回顾下 toString valueO f 方法,方便我们更深入去了解这类型的问题:


比如我们有一个对象,在不重写 toString() 方法和 valueOf() 方法的情况下,在 Node 或者浏览器输出的结果是这样的

class Person {
constructor() {
this.name = name;
}
}

const best = new Person("Kobe");
console.log(best); // log: Person {name: "Kobe"}
console.log(best.toString()); // log: [object Object]
console.log(best.valueOf()); // log: Person {name: "Kobe"}
console.log(best + "GiGi"); // log: [object Object]GiGi


从上面的输出我们可以观察到一个细节, toString() 输出的是 [object Object] ,而 valueOf() 输出的是 Person 对象本身,而当运算到 best + 'GiGi' 的时候竟然是输出了 [object Object]GiGi ,我们可以初步推断是对象调用的 toStrin g() 方法得到的字符串进行计算的,难道是运算符 + 的鬼斧神工吗?

为了验证我们上一步的推断,我们稍微做一点改变,把 valueOf 方法进行一次复写:

class Person {
constructor(name) {
this.name = name;
}
// 复写 valueOf 方法
valueOf() {
return this.name;
}
}

这次跟上面只有一处产生了不一样的结果,那就是最后的 best + 'GiGi' 前后两次结果在复写了 valueOf() 方法之后发生了改变,从中我们可以看出来,对象的本质其实没有发生根本的改变,但是当它被用作直接运算的时候,它的值是从复写的 valueOf() 中获取的,并继续参与后续的运算。

我们发现 best + 'GiGi' 还是没有发生任何改变,还是使用我们上一次复写 val ueOf() 的结果。

其实我们重写了 valueOf 方法,不是一定调用 valueOf() 的返回值进行计算的。而是 value Of 返回的值是基本数据类型时才会按照此值进行计算,如果不是基本数据类型,则将使用 toString() 方法返回的值进行计算。

class Person {
constructor(name) {
this.name = name;
}
valueOf() {
return this.name;
}
toString() {
return `Bye ${this.name}`;
}
}
const best = new Person({ name: "Kobe" });

console.log(best); // log: Person name: {name: "Kobe"}
console.log(best.toString()); // log: Bye [object Object]
console.log(best.valueOf()); // log: Person {name: "Kobe"}
console.log(best + "GiGi"); // log: [object Object]10

看上面的例子,现在传入的 name 是一个对象 new Person({ name: "Kobe" }) ,并不是基本数据类型,所以当执行加法运算的时候取 toString() 方法返回的值进行计算,当然如果没有 valueOf() 方法,就会去执行 toString() 方法。

所以铺垫了这么久,我们就要揭开答案,我们正是使用上面这些原理去解答这一题:

class A {
constructor(value) {
this.value = value;
}
toString() {
return this.value++;
}
}
const a = new A(1);
if (a == 1 && a == 2 && a == 3) {
console.log("Hi Eno!");
}
这里就比较简单,直接改写 toString() 方法,由于没有 valueOf() ,当他做运算判断 a == 1 的时候会执行 toStrin g() 的结果。
class A {
constructor(value) {
this.value = value;
}
valueOf() {
return this.value++;
}
}
const a = new A(1);
if (a == 1 && a == 2 && a == 3) {
console.log("Hi Eno!");
}
当然,你也可以不使用 toString ,换成 valueOf 也行,效果也是一样的:
class A {
constructor(value) {
this.value = value;
}
valueOf() {
return this.value++;
}
}

const a = new A(1);
console.log(a);
if (a == 1 && a == 2 && a == 3) {
console.log("Hi Eno!");
}
所以,当一个对象在做运算的时候 (比如加减乘除,判断相等) 时候,往往会有 valueOf() 或者 toString 的调用问题,这个对象的变量背后通常隐藏着一个函数。

当然下面这题原理其实也是一样的,附上解法:
// 设置一个函数输出一下的值
f(1) = 1;
f(1)(2) = 2;
f(1)(2)(3) = 6;

function f() {
let args = [...arguments];
let add = function() {
args.push(...arguments);
return add;
};
add.toString = function() {
return args.reduce((a, b) => {
return a + b;
});
};
return add;
}
console.log(f(1)(2)(3)); // 6
当然还没有结束,这里还会有一些特别的解法,其实在使用对象的时候,如果对象是一个数组的话,那么上面的逻辑还是会成立,但此时的 toString() 会变成隐式调用 join() 方法,换句话说,对象中如果是数组,当你不重写其它的 toString() 方法,其默认实现就是调用数组的 join() 方法返回值作为 toString() 的返回值,所以这题又多了一个新的解法,就是在不复写 toString() 的前提下,复写 join() 方法,把它变成 shift() 方法,它能让数组的第一个元素从其中删除,并返回第一个元素的值。

class A extends Array {
join = this.shift;
}
const a = new A(1, 2, 3);
if (a == 1 && a == 2 && a == 3) {
console.log("Hi Eno!");
}
我们的探寻之路还没结束,细心的同学会发现我们题目是如何让






请到「今天看啥」查看全文