大厂面试题分享:如何让(a===1&&a===2&&a===3)的值为true?

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

个人思路来源于更早前遇到的另一题类似的面试题:github

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

当时的解决办法是使用toString或者valueOf实现的,那咱们先回顾下toStringvalueOf方法,方便咱们更深刻去了解这类型的问题:面试

好比咱们有一个对象,在不重写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
复制代码
best Person
best.toString() [object Object]
best.valueOf() Person
best + 'GiGi' [object Object]GiGi

从上面的输出咱们能够观察到一个细节,toString()输出的是[object Object],而valueOf()输出的是Person对象自己,而当运算到best + 'GiGi'的时候居然是输出了[object Object]GiGi,咱们能够初步推断是对象调用的toString()方法获得的字符串进行计算的,难道是运算符+的鬼斧神工吗?数组

为了验证咱们上一步的推断,咱们稍微作一点改变,把 valueOf 方法进行一次复写:浏览器

class Person {
  constructor(name) {
    this.name = name;
  }
  // 复写 valueOf 方法
  valueOf() {
    return this.name;
  }
}
复制代码
best Person
best.toString() [object Object]
best.valueOf() Person
best + 'GiGi' KobeGiGi

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

固然不要忘了咱们还有个toString()方法,因此咱们也复写它,看看结果会不会也受影响:工具

class Person {
  constructor(name) {
    this.name = name;
  }
  valueOf() {
    return this.name;
  }
  toString() {
    return `Bye ${this.name}`;
  }
}
复制代码
best Person
best.toString() Bye Kobe
best.valueOf() Kobe
best + 'GiGi' KobeGiGi

咱们发现 best + 'GiGi'仍是没有发生任何改变,仍是使用咱们上一次复写valueOf()的结果ui

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

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]GiGi
复制代码
best Person
best.toString() Bye [object Object]
best.valueOf() {name: "Kobe"}
best + 'GiGi' Bye [object Object]GiGi

看上面的例子,如今传入的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的时候会执行toString()的结果。

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) = 3;
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!");
}
复制代码

咱们的探寻之路还没结束,细心的同窗会发现咱们题目是如何让(a===1&&a===2&&a===3)的值为 true,可是上面都是讨论宽松相等==的状况,在严格相等===的状况下,上面的结果会不一样吗?

答案是不同的,大家能够试试把刚才上面的宽松条件改为严格调试再试一次就知道结果了。

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!"); // Hi Eno!此时再也没出现过了
}
复制代码

那么此时的状况又要怎么去解决呢?咱们能够考虑一下使用Object.defineProperty来解决,这个由于Vue而被众人熟知的方法,也是如今面试中一个老生常谈的知识点了,咱们可使用它来劫持a变量,当咱们获取它的值得时候让它自增,那么问题就能够迎刃而解了:

var value = 1;
Object.defineProperty(window, "a", {
  get() {
    return this.value++;
  }
});

if (a === 1 && a === 2 && a === 3) {
  console.log("Hi Eno!");
}
复制代码

上面咱们就是劫持全局window上面的a,当a每一次作判断的时候都会触发get属性获取值,而且每一次获取值都会触发一次函数实行一次自增,判断三次就自增三次,因此最后会让公式成立。

固然这里还有其余方法,这里再举例一个,好比使用隐藏字符去作障眼法瞒过面试官的:

var aᅠ = 1;
var a = 2;
var ᅠa = 3;
if (aᅠ == 1 && a == 2 && ᅠa == 3) {
  console.log("Hi Eno!");
}
复制代码

上面这种解法的迷惑性很强,若是不细心会觉得是三个同样的a,其实本质上是定义三个不同的a值,a的先后都有隐藏的字符,因此调试的时候,请复制粘贴上面的代码调试,本身在Chrome手打的话能够用特殊手段让 a 后面放一个或者两个红点实现,并在回车的时候,调试工具会把这些痕迹给隐藏,从而瞒天过海,秀到一时半刻还没反应过来的面试官。

最后,祝愿你们在新的一年找到一份如意的工做,上面的代码在实际状况中基本是不会被运用到的,可是用来探索JS的无限多是具备启发性的,也建议面试官不要使用这类面试题去难为面试者~

若是文章和笔记能带您一丝帮助或者启发,请不要吝啬你的赞和收藏,你的确定是我前进的最大动力😁

附笔记连接,阅读往期更多优质文章可移步查看,喜欢的能够给我点赞鼓励哦: