最近,来到了一个新的项目组,在熟悉代码的过程中偶然发现了一个潜藏的 小炸弹。通常情况下,这个问题并不会触发错误或异常,然而,从系统健壮性的角度来看,它的表现并不理想。
具体而言,这段代码的问题在于它使用了null值来进行比较操作。
function sortArray(values) {
if (values != null) {
// 不要这样比较!
values.sort(comparator);
}
}
这里引入一段《JavaScript高级程序设计》一书中的一段原话。
不要比较 null
JavaScript 不会自动做任何类型检查,因此就需要开发者担起这个责任。结果,很多 JavaScript 代码 不会做类型检查。最常见的类型检查是看值是不是 null。然而,与 null 进行比较的代码太多了,其 中很多因为类型检查不够而频繁引发错误。
我就想着,千里之堤,溃于蚁穴,可不能因为这一点小问题闹出大笑话呀,就从这里写个小结吧。
在 “红宝书” 的推荐方法来说,他推荐使用 instanceof 。
现实当中,单纯比较 null 通常是不够的。检查值的类型就要真的检查类型,而不是检查它不能是 什么。例如,在前面的代码中,values 参数应该是数组。为此,应该检查它到底是不是数组,而不是 检查它不是 null。可以像下面这样重写那个函数:
function sortArray(values) { if (values instanceof Array) { // 推荐 values.sort(comparator); } }
嗯嗯,确实 instanceof 有很多大用处的地方,我就对他展开说说吧。
📖 正文
引MDN文档链接:instanceof - JavaScript | MDN (mozilla.org)
instanceof 是 JavaScript 中用于判断一个对象是否是某个构造函数的实例的运算符。它通过检查对象的原型链来确认对象是否继承了某个构造函数的 prototype 属性。与常见的类型判断方式不同,instanceof 更适合用于复杂的对象和类的类型检测,尤其是在继承体系中。
下面我会详细解释 instanceof 的工作原理、用法、常见场景及一些细节注意事项。
1. instanceof 的基本语法
object instanceof Constructor
object:表示要检测的对象。Constructor:表示构造函数或类。
返回值为布尔值,如果 object 是 Constructor 的实例,则返回 false。
2.instanceof 的工作原理
instanceof 并不是简单的检查对象的构造函数,而是通过检查对象的原型链来确认对象是否继承了某个构造函数的prototype 属性。
objct instanceof Constructor的操作过程是这样的:- 获取
Constructor.prototype。 - 检查
object的原型链,看看是否有任何对象等于Constructor.prototype。 - 如果在原型链中找到了
Constructor.prototype,返回true,否则,返回false。
- 获取
3.示例:基础用法
function Animal() {}
let dog = new Animal();
console.log(dog instanceof Animal); // true
console.log(dog instanceof Object); // true
-
dog是通过new Animal()创建的,因此它的原型链中有Animal.prototype,所以dog instanceof Animal返回true。 -
由于
Animal是继承自Object,所以dog的原型链中也包含Object.prototype,因此dog instanceof Object也是true。
4. instanceof 结合类的使用
在 ES6 中,类是一种构造函数的语法糖,本质上仍然是通过原型链实现继承的,因此 instanceof 仍然适用。
class Animal {}
class Dog extends Animal {}
let dog = new Dog();
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Object); // true
-
dog instanceof Dog返回true,因为dog的原型链中有Dog.prototype。 -
dog instanceof Animal也返回true,因为Dog继承了Animal,dog的原型链中也包含Animal.prototype。 -
dog instanceof Object也为true,因为在 JavaScript 中,所有对象最终都继承自Object.prototype。
5. 自定义的 instanceof 检查
你可以自定义对象上的 Symbol.hasInstance 方法来控制 instanceof 的行为。
class Animal {
static [Symbol.hasInstance](instance) {
return instance.canBark === true;
}
}
let dog = { canBark: true };
console.log(dog instanceof Animal); // true
通过重写 Animal 类的 Symbol.hasInstance 方法,可以自定义 instanceof 的行为。在这个例子中,任何具有 canBark 属性且值为 true 的对象都会被认为是 Animal 的实例。
6. instanceof 与原型链
instanceof 通过检查原型链来工作,因此对象的原型链变化时,instanceof 的结果也会相应变化。
function Animal() {}
let dog = new Animal();
console.log(dog instanceof Animal); // true
// 改变对象的原型链
Object.setPrototypeOf(dog, {});
console.log(dog instanceof Animal); // false
在 dog 原型链被改变之后,dog 不再是 Animal 的实例,因此 instanceof Animal 返回 false。
7. instanceof 与基本数据类型
instanceof 主要用于对象的检测,对于原始数据类型(如 number, string, boolean, null, undefined),它总是返回 false。
javascript
复制代码
console.log(42 instanceof Number); // false
console.log(new Number(42) instanceof Number); // true
- 原始类型(如
42)并不是Number对象的实例。 - 通过
new Number(42)创建的包装对象才是Number构造函数的实例,因此instanceof返回true。
8. instanceof 与 Array
instanceof 也可以用于检测数组是否是 Array 的实例。
javascript
复制代码
let arr = [1, 2, 3];
console.log(arr instanceof Array); // true
console.log(arr instanceof Object); // true
arr是Array的实例,因此arr instanceof Array返回true。Array是从Object派生出来的,因此arr也是Object的实例,arr instanceof Object返回true。
9. instanceof 与跨 iframe/窗口
在跨窗口或 iframe 的情况下,instanceof 可能会产生误导结果,因为每个 iframe 或窗口都有自己的全局环境和构造函数。
javascript
复制代码
let iframe = document.createElement('iframe');
document.body.appendChild(iframe);
let iframeArray = new iframe.contentWindow.Array();
console.log(iframeArray instanceof Array); // false
console.log(iframeArray instanceof iframe.contentWindow.Array); // true
- 在这个例子中,
iframeArray是在 iframe 的上下文中创建的,因此它的原型链不属于当前窗口的Array,导致iframeArray instanceof Array返回false。 - 但是,
iframeArray instanceof iframe.contentWindow.Array为true,因为它是在 iframe 中创建的Array实例。
10. instanceof 与 Object.prototype.toString
instanceof 并不是判断类型的唯一方法,你也可以使用 Object.prototype.toString 来精确地判断数据类型,特别是在处理跨窗口对象时更加可靠。
javascript
复制代码
let iframe = document.createElement('iframe');
document.body.appendChild(iframe);
let iframeArray = new iframe.contentWindow.Array();
console.log(Object.prototype.toString.call(iframeArray)); // "[object Array]"
11. 常见的误区与陷阱
-
对于非对象,
instanceof始终返回false:javascript 复制代码 console.log(null instanceof Object); // false console.log(undefined instanceof Object); // false -
跨环境问题:
instanceof在不同的窗口或 iframe 之间会出现不一致的结果。使用Object.prototype.toString.call()更为稳妥。
-
对于基础类型包装对象:
new Number(42)是Number的实例,而42不是。
总结
instanceof是用来检测一个对象是否是某个构造函数的实例,通过检查其原型链来判断。- 它在处理继承、复杂对象和类之间的关系时非常有用。
- 但是在跨 iframe 或窗口的情况下使用时需要小心,它可能会因为不同的全局环境而导致结果不一致。