扯皮
前段时间看红宝书引用类型中的数组篇章时,其中有一个关于判断数组的小节提到了 instanceof 的一个弊端,这里赶紧写一篇水文做个总结,以后和面试官深情交流时就可以再多讲一些,拿下的机会更高了!
正文
instanceof
关于 instanceof 就是用来判断一些引用类型对象是否由某个构造函数所构造出来,但准确来讲是检测该对象的原型链上是否存在该构造函数的 prototype
根据这一特性我们完全就能够手写出来一个 instanceof,只需沿着该对象的原型链去判断即可:
javascript
function myInstanceof(o, constructor) {
if (typeof o !== "object" || o === null) return false;
const prototype = constructor.prototype;
let proto = o.__proto__;
while (proto) {
if (proto === prototype) return true;
proto = proto.__proto__;
}
return false;
}
const arr = [];
console.log(arr instanceof Array); // true
console.log(arr instanceof Object); // true
console.log(arr instanceof RegExp); // false
// my
console.log(myInstanceof(arr, Array)); // true
console.log(myInstanceof(arr, Object)); // true
console.log(myInstanceof(arr, RegExp)); // false
两个弊端
篡改原型链
关于这一点我相信是大部分人都知道的,包括我个人在面试的时候也都会提到这一点来说明 instanceof 本身的局限性。
由于 instanceof 是沿着原型链去判断,因此当我们尝试修改原有原型链的指向就会造成判断错误:
javascript
function Person() {}
function Monster() {}
const p = new Person();
console.log(p instanceof Person); // true
Person.prototype = Monster.prototype;
console.log(p instanceof Person); // false
console.log(p.__proto__ === Person.prototype); // false
但我们知道针对于一些内置的构造函数:Object、Array、Date 等它们的 prototype 是不允许进行修改的,因此可以大胆的进行使用 instanceof 进行判断,我们直接来看这些构造函数针对于 prototype 属性的属性描述符:
javascript
console.log(Object.getOwnPropertyDescriptor(Object, "prototype"));
console.log(Object.getOwnPropertyDescriptor(Array, "prototype"));
console.log(Object.getOwnPropertyDescriptor(Date, "prototype"));
console.log(Object.getOwnPropertyDescriptor(RegExp, "prototype"));
console.log(Object.getOwnPropertyDescriptor(Map, "prototype"));
console.log(Object.getOwnPropertyDescriptor(Set, "prototype"));
结果理所当然,清一色的 false:
所以这种操作就别想辣:
javascript
const cache = Array.prototype;
const emptyObj = Object.create(null);
Array.prototype = emptyObj; // no use ❗
console.log(Array.prototype === emptyObj, Array.prototype === cache); // false true
console.log([] instanceof Array); // true
看似这里 instanceof 好像没其他问题可以放心用了,直到我看到了红宝书里面的内容,我们继续往下研究。
同一个全局执行上下文
在红宝书中关于 instanceof 又提到了另外一个点指明它也并不是万能的:
针对于 instanceof 这里只提到了这一段话,并且提到了一个关键词:全局执行上下文,我们自然而然的可以想象的到是网页的 window 对象,所以根据这段话来推理就可能出现这样的情景,如下图所示:
一个页面有多个全局对象,如果是来自不同对象的变量进行判断就不再符合预期了:
关于这一点我查阅了 MDN 文档,针对 instanceof 的这个问题也做了解释(怪自己学习的时候没仔细看,全看八股去了😭)instanceof - JavaScript | MDN (mozilla.org)
OK,眼见为实,我们自己来模拟一下一下 iframe
的场景,开启 live Server 在一个 html 中通过 iframe
引入另外一个页面:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h1>page1</h1>
<!-- 引入 iframe -->
<iframe src="./two.html"></iframe>
<script>
const arr = [];
const window2 = window.frames[0]; // 拿到 iframe window 对象
console.log(arr instanceof window2.Array);
</script>
</body>
</html>
总结
instanceof 可以用作引用类型的判断,但是它自身也有局限性,比如内部是根据原型链的顺序来判断,如果人为修改原型链会导致判断错误。即使 JS 的内置构造函数 Array、Object 等的 prototype 属性是不可修改的,但如果遇到多个执行上下文的场景(iframe)并牵扯到页面之间的数据通信,也可能会造成 instanceof 判断错误。
综上所述判断类型要斟酌当前的场景再合理的使用 instanceof,当然也可以使用 Array.isArray
或者 Object.prototype.toString.call
来进行判断,更加准确和安全。