JavaScript instanceof 运算符详解:工作原理、用法及常见陷阱

189 阅读5分钟

最近,来到了一个新的项目组,在熟悉代码的过程中偶然发现了一个潜藏的 小炸弹。通常情况下,这个问题并不会触发错误或异常,然而,从系统健壮性的角度来看,它的表现并不理想。

具体而言,这段代码的问题在于它使用了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:表示构造函数或类。

返回值为布尔值,如果 objectConstructor 的实例,则返回 false

2.instanceof 的工作原理

instanceof 并不是简单的检查对象的构造函数,而是通过检查对象的原型链来确认对象是否继承了某个构造函数的prototype 属性。

  • objct instanceof Constructor 的操作过程是这样的:
    1. 获取Constructor.prototype
    2. 检查object 的原型链,看看是否有任何对象等于Constructor.prototype
    3. 如果在原型链中找到了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 继承了 Animaldog 的原型链中也包含 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. instanceofArray

instanceof 也可以用于检测数组是否是 Array 的实例。

javascript
复制代码
let arr = [1, 2, 3];
console.log(arr instanceof Array);    // true
console.log(arr instanceof Object);   // true
  • arrArray 的实例,因此 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.Arraytrue,因为它是在 iframe 中创建的 Array 实例。

10. instanceofObject.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 或窗口的情况下使用时需要小心,它可能会因为不同的全局环境而导致结果不一致。