2021 年 12 月 12 日

类检查:“instanceof”

instanceof 运算符允许检查对象是否属于某个类。它还考虑了继承。

在许多情况下可能需要进行这样的检查。例如,它可用于构建多态函数,该函数根据参数的类型对参数进行不同的处理。

instanceof 运算符

语法为

obj instanceof Class

如果 obj 属于 Class 或继承自 Class 的类,则它返回 true

例如

class Rabbit {}
let rabbit = new Rabbit();

// is it an object of Rabbit class?
alert( rabbit instanceof Rabbit ); // true

它还适用于构造函数

// instead of class
function Rabbit() {}

alert( new Rabbit() instanceof Rabbit ); // true

…以及内置类,如 Array

let arr = [1, 2, 3];
alert( arr instanceof Array ); // true
alert( arr instanceof Object ); // true

请注意,arr 也属于 Object 类。这是因为 Array 在原型上继承自 Object

通常,instanceof 会检查原型链以进行检查。我们还可以在静态方法 Symbol.hasInstance 中设置自定义逻辑。

obj instanceof Class 算法大致如下

  1. 如果有静态方法 Symbol.hasInstance,则直接调用它:Class[Symbol.hasInstance](obj)。它应该返回 truefalse,我们完成。这就是我们可以自定义 instanceof 行为的方式。

    例如

    // setup instanceOf check that assumes that
    // anything with canEat property is an animal
    class Animal {
      static [Symbol.hasInstance](obj) {
        if (obj.canEat) return true;
      }
    }
    
    let obj = { canEat: true };
    
    alert(obj instanceof Animal); // true: Animal[Symbol.hasInstance](obj) is called
  2. 大多数类没有 Symbol.hasInstance。在这种情况下,使用标准逻辑:obj instanceOf Class 检查 Class.prototype 是否等于 obj 原型链中的某个原型。

    换句话说,逐个比较

    obj.__proto__ === Class.prototype?
    obj.__proto__.__proto__ === Class.prototype?
    obj.__proto__.__proto__.__proto__ === Class.prototype?
    ...
    // if any answer is true, return true
    // otherwise, if we reached the end of the chain, return false

    在上面的示例中,rabbit.__proto__ === Rabbit.prototype,所以立即给出了答案。

    在继承的情况下,匹配将在第二步进行

    class Animal {}
    class Rabbit extends Animal {}
    
    let rabbit = new Rabbit();
    alert(rabbit instanceof Animal); // true
    
    // rabbit.__proto__ === Animal.prototype (no match)
    // rabbit.__proto__.__proto__ === Animal.prototype (match!)

以下是 rabbit instanceof AnimalAnimal.prototype 比较的说明

顺便说一下,还有一个方法 objA.isPrototypeOf(objB),如果 objAobjB 的原型链中的某个位置,则返回 true。因此,obj instanceof Class 的测试可以改写为 Class.prototype.isPrototypeOf(obj)

有趣的是,Class 构造函数本身不参与检查!只有原型链和 Class.prototype 才重要。

当在创建对象后更改 prototype 属性时,可能会导致有趣的后果。

就像这里

function Rabbit() {}
let rabbit = new Rabbit();

// changed the prototype
Rabbit.prototype = {};

// ...not a rabbit any more!
alert( rabbit instanceof Rabbit ); // false

奖励:Object.prototype.toString 用于类型

我们已经知道普通对象被转换为字符串为 [object Object]

let obj = {};

alert(obj); // [object Object]
alert(obj.toString()); // the same

这是它们对 toString 的实现。但是有一个隐藏的功能,使得 toString 实际上比这强大得多。我们可以将它用作扩展的 typeofinstanceof 的替代品。

听起来很奇怪?确实如此。让我们揭开谜底。

根据 规范,可以从对象中提取内置 toString,并在任何其他值的环境中执行。其结果取决于该值。

  • 对于数字,它将是 [object Number]
  • 对于布尔值,它将是 [object Boolean]
  • 对于 null[object Null]
  • 对于undefined[object Undefined]
  • 对于数组:[object Array]
  • …等等(可自定义)。

我们来演示一下

// copy toString method into a variable for convenience
let objectToString = Object.prototype.toString;

// what type is this?
let arr = [];

alert( objectToString.call(arr) ); // [object Array]

这里我们使用了call,如装饰器和转发,call/apply一章中所述,在上下文this=arr中执行函数objectToString

在内部,toString算法检查this并返回相应的结果。更多示例

let s = Object.prototype.toString;

alert( s.call(123) ); // [object Number]
alert( s.call(null) ); // [object Null]
alert( s.call(alert) ); // [object Function]

Symbol.toStringTag

可以使用特殊对象属性Symbol.toStringTag自定义 Object toString的行为。

例如

let user = {
  [Symbol.toStringTag]: "User"
};

alert( {}.toString.call(user) ); // [object User]

对于大多数环境特定的对象,都有这样的属性。以下是一些浏览器特定的示例

// toStringTag for the environment-specific object and class:
alert( window[Symbol.toStringTag]); // Window
alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest

alert( {}.toString.call(window) ); // [object Window]
alert( {}.toString.call(new XMLHttpRequest()) ); // [object XMLHttpRequest]

如你所见,结果恰好是Symbol.toStringTag(如果存在),包装在[object ...]中。

最后,我们有了“类固醇上的typeof”,它不仅适用于原始数据类型,还适用于内置对象,甚至可以自定义。

当我们希望将类型获取为字符串而不是仅仅检查时,我们可以对内置对象使用{}.toString.call代替instanceof

总结

让我们总结一下我们所知道的类型检查方法

适用于 返回
typeof 基本类型 字符串
{}.toString 基本类型、内置对象、具有Symbol.toStringTag的对象 字符串
instanceof 对象 true/false

正如我们所看到的,{}.toString在技术上是一个“更高级”的typeof

当我们使用类层次结构并希望检查考虑继承的类时,instanceof运算符真正发挥了作用。

任务

重要性:5

在下面的代码中,为什么instanceof返回true?我们可以很容易地看到a不是由B()创建的。

function A() {}
function B() {}

A.prototype = B.prototype = {};

let a = new A();

alert( a instanceof B ); // true

是的,看起来确实很奇怪。

但是instanceof不关心函数,而关心它的prototype,它与原型链匹配。

这里a.__proto__ == B.prototype,所以instanceof返回true

因此,根据instanceof的逻辑,prototype实际上定义了类型,而不是构造函数。

教程地图

评论

在评论之前阅读此内容…
  • 如果您有改进建议,请 提交 GitHub 问题 或提交拉取请求,而不是发表评论。
  • 如果您无法理解文章中的某些内容,请详细说明。
  • 要插入几行代码,请使用 <code> 标记,对于多行,请用 <pre> 标记将其包装起来,对于 10 行以上的内容,请使用沙箱 (plnkrjsbincodepen…)