前言
原题:(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」
那么这三个方法的调用顺序是什么样呢?实操验证一下。
重写3个方法,都只打上console.log,执行结果发现只有Symbol.toPrimitive 方法执行了。之前对这个方法不是很了解,然后就疯狂谷歌了一轮。
**Symbol.toPrimitive**
是一个内置的 Symbol 值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数。—— MDN
去掉 Symbol.toPrimitive
去掉valueOf
实操发现,三个方法的优先级: 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 劫持
一开始没想明白这个劫持功能怎么去实现问题的解法。转念一想,之前好像记得在当前全局变量上定义某个属性就可以直接取出来,比如下图。
于是:
let tmp = 1;
Object.defineProperty(window, "a", {
get: function () {
return tmp++;
},
});
console.log(a === 1 && a === 2 && a === 3);
这是在浏览器的控制台执行的结果,如果要是在node环境执行呢?node环境可没有window变量。
globalThis.tmp = 1;
Object.defineProperty(globalThis, "a", {
get: function () {
return globalThis.tmp++;
},
});
console.log(a === 1 && a === 2 && a === 3);
- 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);
总结
遇到没有思路的问题尝试思考下考官到底想考察我们什么知识,跳出思维定势,最后基本功还是要扎实。