「前端面试题」(a ==1 && a== 2 && a==3) 有可能是 true 吗?

乐闻世界

前言

原题:(a 1 && a 2 && a==3) 有可能是 true 吗?

这是一道有意思的面试题。盲猜很多小伙伴看到这道面试题时肯定是一头雾水,a难道还能同时等于多个值?但是既然这道题能出现,说明肯定是想考察点什么知识。

「理论分析」

== 会将左右两边的值转化成相同的原始类型,然后再去比较他们是否相等。

看到这句话肯定有很多朋友恍然大悟。a == 1 不一定 a 就是数字类型1,而是 a 可能是个对象,然后在 == 相等操作时,会隐式调用某些方法,这些方法返回 1。然后就给我们 a == 1这样的错觉。

「实操环节」

  • toString

    let a = {
      i: 1,
      toString: function () {
        return this.i++;
      },
    };
    console.log(a == 1 && a == 2 && a == 3);
    
  • valueOf

    let a = {
      i: 1,
      valueOf: function () {
        return this.i++;
      },
    };
    console.log(a == 1 && a == 2 && a == 3);
    
  • [Symbol.toPrimitive]

    let a = {
      i: 1,
      [Symbol.toPrimitive]: (i) => {
        return a.i++;
      },
    };
    console.log(a == 1 && a == 2 && a == 3);
    

备注

看到上面的三种实现方式,相信大家看出来相同点,就是在 a 对象中重写了默认属性或方法 「toString」「valueOf」「Symbol.toPrimitive」

那么这三个方法的调用顺序是什么样呢?实操验证一下。

image-20201103151315162

重写3个方法,都只打上console.log,执行结果发现只有Symbol.toPrimitive 方法执行了。之前对这个方法不是很了解,然后就疯狂谷歌了一轮。

**Symbol.toPrimitive** 是一个内置的 Symbol 值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数。—— MDN

去掉 Symbol.toPrimitive

image-20201103152129952

去掉valueOf

image-20201103152256524

实操发现,三个方法的优先级: Symbol.toPrimitive > valueOf > toString


扯远了,继续讨论有没有其他的方式解决开篇的问题。

如果 a 是一个数组时,a 执行 == 松散相等操作会默认调用什么方法呢? 数组会隐式调用 join 方法。那么骚操作又来了

let a = [1, 2, 3];
a.join = a.shift;
console.log(a == 1 && a == 2 && a == 3);

重新把 join 指向 shift方法,shift 是弹出数组首位元素,是一个副作用函数。

相同的思路:

let a = [3, 2, 1];
a.join = a.pop;
console.log(a == 1 && a == 2 && a == 3);

通过重写默认方法的方式解决问题大致思路就这些,下面再提供一些类似的解法,以丰富思路。

  • 重写 Number 对象的 valueOf
var i = 1;
Number.prototype.valueOf = function () {
  return i++;
};
var a = new Number(1);
console.log(a == 1 && a == 2 && a == 3);

这种写法会存在污染全局变量的问题,那么这个时候就可以把你背的滚瓜乱熟的闭包知识运用上了。

Number.prototype.valueOf = (function () {
  let i = 1;
  return () => {
    return i++;
  };
})();
let a = new Number(1);
console.log(a == 1 && a == 2 && a == 3);

  • Object.defineProperty 劫持

一开始没想明白这个劫持功能怎么去实现问题的解法。转念一想,之前好像记得在当前全局变量上定义某个属性就可以直接取出来,比如下图。

image-20201103162744570

于是:

let tmp = 1;
Object.defineProperty(window, "a", {
  get: function () {
    return tmp++;
  },
});
console.log(a === 1 && a === 2 && a === 3);
image-20201103163029128

这是在浏览器的控制台执行的结果,如果要是在node环境执行呢?node环境可没有window变量。

globalThis.tmp = 1;
Object.defineProperty(globalThis, "a", {
  get: function () {
    return globalThis.tmp++;
  },
});
console.log(a === 1 && a === 2 && a === 3);
image-20201103163245296
  • ES6 Proxy

既然Object.defineProperty 是运用了数据劫持的原理,那么ES6的 proxy 也可以实现如下问题的解法。

let a = new Proxy(
  {},
  {
    tmp: 1,
    get(target, key) {
      return () => this.tmp++;
    },
  }
);
console.log(a == 1 && a == 2 && a == 3);

总结

遇到没有思路的问题尝试思考下考官到底想考察我们什么知识,跳出思维定势,最后基本功还是要扎实。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乐闻x

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值