类检查 instanceof
instanceof
操作符用于检查一个对象是否属于某个特定的 class。同时,它还考虑了继承。
在许多情况下,可能都需要进行此类检查。例如,它可以被用来构建一个 多态性(polymorphic) 的函数,该函数根据参数的类型对参数进行不同的处理。
instanceof 操作符
语法:
obj instanceof Class
如果 obj
隶属于 Class
类(或 Class
类的衍生类),则返回 true
。
例如:
class Rabbit { }
let rabbit = new Rabbit();
// rabbit 是 Rabbit class 的对象吗?
console.log(rabbit instanceof Rabbit); // true
它还可以与构造函数一起使用:
// 这里是构造函数,而不是 class
function Rabbit() { }
console.log(new Rabbit() instanceof Rabbit); // true
……与诸如 Array
之类的内建 class 一起使用:
let arr = [1, 2, 3];
console.log(arr instanceof Array); // true
console.log(arr instanceof Object); // true
有一点需要留意,arr
同时还隶属于 Object
类。因为从原型上来讲,Array
是继承自 Object
的。
通常,instanceof
在检查中会将原型链考虑在内。此外,我们还可以在静态方法 Symbol.hasInstance
中设置自定义逻辑。
obj instanceof Class
算法的执行过程大致如下:
如果这儿有静态方法
Symbol.hasInstance
,那就直接调用这个方法:例如:
js// 设置 instanceOf 检查 // 并假设具有 canEat 属性的都是 animal class Animal { static [Symbol.hasInstance](obj) { console.log('我被调用了'); if (obj.canEat) return true; } } let obj = { canEat: true }; console.log(obj instanceof Animal); // true:Animal[Symbol.hasInstance](obj) 被调用
大多数 class 没有
Symbol.hasInstance
。在这种情况下,标准的逻辑是:使用obj instanceOf Class
检查Class.prototype
是否等于obj
的原型链中的原型之一。换句话说就是,一个接一个地比较:
jsobj.__proto__ === Class.prototype? obj.__proto__.__proto__ === Class.prototype? obj.__proto__.__proto__.__proto__ === Class.prototype? ... // 如果任意一个的答案为 true,则返回 true // 否则,如果我们已经检查到了原型链的尾端,则返回 false
在上面那个例子中,
rabbit.__proto__ === Rabbit.prototype
,所以立即就给出了结果。而在继承的例子中,匹配将在第二步进行:
jsclass Animal {} class Rabbit extends Animal {} let rabbit = new Rabbit(); console.log(rabbit instanceof Animal); // true // rabbit.__proto__ === Animal.prototype(无匹配) // rabbit.__proto__.__proto__ === Animal.prototype(匹配!)
下图展示了
rabbit instanceof Animal
的执行过程中,Animal.prototype
是如何参与比较的:这里还要提到一个方法 objA.isPrototypeOf(objB),如果
objA
处在objB
的原型链中,则返回true
。所以,可以将obj instanceof Class
检查改为Class.prototype.isPrototypeOf(obj)
。这很有趣,但是
Class
的 constructor 自身是不参与检查的!检查过程只和原型链以及Class.prototype
有关。创建对象后,如果更改
prototype
属性,可能会导致有趣的结果。就像这样:
jsfunction Rabbit() {} let rabbit = new Rabbit(); // 修改了 prototype Rabbit.prototype = {}; // ...再也不是 rabbit 了! console.log( rabbit instanceof Rabbit ); // false
福利:使用Object.prototype.toString方法来揭示类型
大家都知道,一个普通对象被转化为字符串时为 [object Object]
:
let obj = {};
console.log(obj.toString()); // [object Object]
这是通过 toString
方法实现的。但是这儿有一个隐藏的功能,该功能可以使 toString
实际上比这更强大。我们可以将其作为 typeof
的增强版或者 instanceof
的替代方法来使用。
听起来挺不可思议?那是自然,精彩马上揭晓。
按照 规范 所讲,内建的 toString
方法可以被从对象中提取出来,并在任何其他值的上下文中执行。其结果取决于该值。
- 对于 number 类型,结果是
[object Number]
- 对于 boolean 类型,结果是
[object Boolean]
- 对于
null
:[object Null]
- 对于
undefined
:[object Undefined]
- 对于数组:
[object Array]
- ……等(可自定义)
让我们演示一下:
// 方便起见,将 toString 方法复制到一个变量中
let objectToString = Object.prototype.toString;
// 它是什么类型的?
let arr = [];
console.log(objectToString.call(arr)); // [object Array]
这里我们用到了在 装饰器模式和转发,call/apply 一章中讲过的 call 方法来在上下文 this=arr
中执行函数 objectToString
。
在内部,toString
的算法会检查 this
,并返回相应的结果。再举几个例子:
let s = Object.prototype.toString;
console.log(s.call(123)); // [object Number]
console.log(s.call(null)); // [object Null]
console.log(s.call(console.log)); // [object Function]
Symbol.toStringTag
可以使用特殊的对象属性 Symbol.toStringTag
自定义对象的 toString
方法的行为。
例如:
let user = {
[Symbol.toStringTag]: "User"
};
console.log({}.toString.call(user)); // [object User]
console.log(user.toString()); // [object User]
对于大多数特定于环境的对象,都有一个这样的属性。下面是一些特定于浏览器的示例:
// 特定于环境的对象和类的 toStringTag:
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
。
总结
之前在学习instanceof的时候知道,obj instanceof A
,就是检测,obj
的原型链上,是否能找到A.prototype
,有时候我们需要这样去判断一下,再去调用某个方法,以确保安全性。
instanceof 可以用来检测 对象的原型链上是否有某个类的原型。
jsclass A { } let o1 = new A() console.log(o1 instanceof A); // true // 因为 o1.__proto__ === A.prototype
实际上,在判断
obj instanceof class
时,会先看class上有没有Symbol.hasInstance
方法,会根据这个方法来返回true或false。jsfunction A() { } let o1 = new A() console.log(o1 instanceof A); // true o1['cheny'] = true // 给这个对象新增一个 cheny 的属性 class B { static [Symbol.hasInstance](obj) { // 如果一个对象有这个属性,就返回true console.log('这个方法被调用了'); if (obj.cheny) return true } } console.log(o1 instanceof B); // 这个也返回了 true /* 这个方法被调用了 true */
但是大多数的class是没有
[Symbol.hasInstance]
的,所以还是走之前的逻辑,obj instanceof class
,判断obj的原型链上是否有class.prototype
有一个Object的方法,也有类似于instanceof的效果,
objA.isPrototypeOf(objB)
,但是有区别,obj instanceof class
,检测的obj的原型链上是否有class.prototype
,而不是针对于class
本身,而objA.isPrototypeOf(objB)
是检测的本身。jsclass A { } let o1 = new A() console.log(o1 instanceof A); // true // 下面这两个调用方式一样的,最后返回的false,就是因为isPrototypeOf方法比较的是对象本身 // 并且 obj1.isPrototypeOf(obj2),判断的是obj1是不是obj2的原型,与instanceof的参数也是反着的 // obj instanceof class,判断的是 class.prototype在不在 obj的原型链上 // 而 instanceof 比较的是 原型prototype console.log(Object.prototype.isPrototypeOf.call(A, o1)); // false console.log(A.isPrototypeOf(o1)); // false // 换一种写法就会返回true了 console.log(Object.prototype.isPrototypeOf.call(A.prototype, o1)); // true console.log(A.prototype.isPrototypeOf(o1)); // true
使用
Object.prototype.toString.call(obj)
,可以打印出任何对象的实际类型。jslet s = Object.prototype.toString; console.log(s.call(123)); // [object Number] console.log(s.call(null)); // [object Null] console.log(s.call(console.log)); // [object Function]
实际上可以使用
Symbol.toStringTag
,来定义Object.prototype.toString.call(obj)
打印的结果jslet user = { [Symbol.toStringTag]: "User" }; console.log({}.toString.call(user)); // [object User] console.log(user.toString()); // [object User]