查看原文
其他

【第1745期】现代 JavaScript 教程 - 类型检测:"instanceof"

LeviDing 前端早读课 2019-10-16

前言

系统的还是可以了解一下。今日早读文章由@LeviDing授权分享。

正文从这开始~~

instanceof 操作符用于检测对象是否属于某个 class,同时,检测过程中也会将继承关系考虑在内。

这种检测在多数情况下还是比较有用的,下面,我们就用它来构建一个具备 多态 性的函数,这个函数能识别出参数类型,从而作出不同的处理。

instanceof

用法:

  1. obj instanceof Class

如果 obj 隶属于 Class 类(或者是 Class 类的衍生类),表达式将返回 true。

举例说明:

  1. class Rabbit {}

  2. let rabbit = new Rabbit();


  3. // rabbit 是 Rabbit 类的实例对象吗?

  4. alert( rabbit instanceof Rabbit ); // true

使用构造函数结果也是一样的:

  1. // 构造函数而非 class

  2. function Rabbit() {}


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

…再来看看内置类型 Array:

  1. let arr = [1, 2, 3];

  2. alert( arr instanceof Array ); // true

  3. alert( arr instanceof Object ); // true

有一点需要留意,arr 同时还隶属于 Object 类。因为从原型上来讲,Array 是继承自 Object 类的。

instanceof 在检测中会将原型链考虑在内,此外,还能借助静态方法 Symbol.hasInstance 来改善检测效果。

obj instanceof Class 语句的大致执行过程如下:

如果提供了静态方法 Symbol.hasInstance,那就直接用这个方法进行检测:

  1. // 假设具有 canEat 属性的对象为动物类

  2. class Animal {

  3. static [Symbol.hasInstance](obj) {

  4. if (obj.canEat) return true;

  5. }

  6. }


  7. let obj = { canEat: true };

  8. alert(obj instanceof Animal); // 返回 true:调用 Animal[Symbol.hasInstance](obj)

大部分的类是没有 Symbol.hasInstance 方法的,这时会检查 Class.prototype 是否与 obj 的原型链中的任何一个原型相等。

简而言之,是这么比较的:

  1. obj.__proto__ === Class.prototype

  2. obj.__proto__.__proto__ === Class.prototype

  3. obj.__proto__.__proto__.__proto__ === Class.prototype

  4. ...

在上一个例子中有 Rabbit.prototype === rabbit.proto 成立,所以结果是显然的。

再比如下面一个继承的例子,rabbit 对象同时也是父类的一个实例:

  1. class Animal {}

  2. class Rabbit extends Animal {}


  3. let rabbit = new Rabbit();

  4. alert(rabbit instanceof Animal); // true

  5. // rabbit.__proto__ === Rabbit.prototype

  6. // rabbit.__proto__.__proto__ === Animal.prototype (match!)

下图展示了 rabbit instanceof Animal 的执行过程中,Animal.prototype 是如何参与比较的:

这里还要提到一个方法 objA.isPrototypeOf(objB),如果 objA 处在 objB 的原型链中,调用结果为 true。所以,obj instanceof Class 也可以被视作为是调用 Class.prototype.isPrototypeOf(obj)。

虽然有点奇葩,其实 Class 的构造器自身是不参与检测的!检测过程只和原型链以及 Class.prototype 有关。

所以,当 prototype 改变时,会产生意想不到的结果。

就像这样:

  1. function Rabbit() {}

  2. let rabbit = new Rabbit();


  3. // 修改其 prototype

  4. Rabbit.prototype = {};


  5. // ...再也不是只兔子了!

  6. alert( rabbit instanceof Rabbit ); // false

所以,为了谨慎起见,最好避免修改 prototype。

福利:使用 Object 的 toString 方法来揭示类型

大家都知道,一个普通对象被转化为字符串时为 [object Object]:

  1. let obj = {};


  2. alert(obj); // [object Object]

  3. alert(obj.toString()); // 同上

这也是它们的 toString 方法的实现如此。但是,toString 自有其潜质,可以让它变得更实用一点。甚至可以用来替代 instanceof,也可以视作为 typeof 的增强版。

听起来挺不可思议?那是自然,精彩马上揭晓。

按照 规范 上所讲,内置的 toString 方法可以从对象中提取出来,以其他值作为上下文(context)对象进行调用,调用结果取决于传入的上下文对象。

  • 如果传入的是 number 类型,返回 [object Number]

  • 如果传入的是 boolean 类型,返回 [object Boolean]

  • 如果传入 null,返回 [object Null]

  • 传入 undefined,返回 [object Undefined]

  • 传入数组,返回 [object Array]

  • …等等(例如一些自定义类型)

下面进行阐述:

  1. // 保存 toString 方法的引用,方便后面使用

  2. let objectToString = Object.prototype.toString;


  3. // 猜猜是什么类型?

  4. let arr = [];


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

这里用到了章节 装饰和转发,call/apply 里提到的 call 方法来调用 this=arr 上下文的方法 objectToString。

toString 的内部算法会检查 this 对象,返回对应的结果。再来几个例子:

  1. let s = Object.prototype.toString;


  2. alert( s.call(123) ); // [object Number]

  3. alert( s.call(null) ); // [object Null]

  4. alert( s.call(alert) ); // [object Function]

Symbol.toStringTag

对象的 toString 方法可以使用 Symbol.toStringTag 这个特殊的对象属性进行自定义输出。

举例说明:

  1. let user = {

  2. [Symbol.toStringTag]: "User"

  3. };


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

大部分和环境相关的对象也有这个属性。以下输出可能因浏览器不同而异:

  1. // 环境相关对象和类的 toStringTag:

  2. alert( window[Symbol.toStringTag]); // window

  3. alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest


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

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

输出结果和 Symbol.toStringTag(前提是这个属性存在)一样,只不过被包裹进了 [object ...] 里。

这样一来,我们手头上就有了个“磕了药似的 typeof”,不仅能检测基本数据类型,就是内置对象类型也不在话下,更可贵的是还支持自定义。

所以,如果希望以字符串的形式获取内置对象类型信息,而不仅仅只是检测类型的话,可以用这个方法来替代 instanceof。

总结

下面,来总结下大家学到的类型检测方式:

看样子,{}.toString 基本就是一增强版 typeof。

instanceof 在涉及多层类结构的场合中比较实用,这种情况下需要将类的继承关系考虑在内。

任务

不按套路出牌的 instanceof

下面代码中,instanceof 为什么会返回 true?很显然,a 并不是通过 B() 创建的。

  1. function A() {}

  2. function B() {}


  3. A.prototype = B.prototype = {};


  4. let a = new A();


  5. alert( a instanceof B ); // true

解决方案

确实挺诡异的。

instanceof 并不关心构造函数,它真正关心的是原型链。

这里有 a._proto_ == B.prototype 成立,所以 instanceof 返回了 true。

总之,按照 instanceof 的逻辑,真正决定类型的是 prototype,而不是构造函数。

关于本文

原文:https://zh.javascript.info/instanceof

为你推荐


【第1295期】浅谈 instanceof 和 typeof 的实现原理


【第1741期】JavaScript和TypeScript中的void


【第1465期】内存管理速成教程

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存