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

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

// 设置一个函数输入一下的值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
bestPerson
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;  }}
bestPerson
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}`;  }}
bestPerson
best.toString()Bye Kobe
best.valueOf()Kobe
best + 'GiGi'KobeGiGi

咱们发现 best + 'GiGi'还是没有产生任何扭转,还是应用咱们上一次复写valueOf()的后果

其实咱们重写了valueOf办法,不是肯定调用valueOf()的返回值进行计算的。而是valueOf返回的值是根本数据类型时才会依照此值进行计算,如果不是根本数据类型,则将应用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]GiGi
bestPerson
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的有限可能是具备启发性的,也倡议面试官不要应用这类面试题去难为面试者~

如果文章和笔记能带您一丝帮忙或者启发,请不要悭吝你的赞和珍藏,你的必定是我后退的最大能源

附笔记链接,浏览往期更多优质文章可移步查看,喜爱的能够给我点赞激励哦:

  • https://github.com/Wscats/CV/...