类型检查方法
一、类型检查方法有:typeof、{}.toString、instanceof
用于 | 返回值 | |
---|---|---|
typeof | 原始数据类型 | string |
{}.toString | 原始数据类型,内建对象,包含 Symbol.toStringTag 属性的对象 |
string |
instanceof | 对象 | true/false |
二、正如我们所看到的,从技术上讲,{}.toString是一种“更高级的”typeof。
三、当我们使用类的层次结构(hierarchy),并想要对该类进行检查,同时还要考虑继承时,这种场景下instanceof操作符确实很出色。
instanceof 操作符
一、instanceof操作符用于检查一个对象是否属于某个特定的 class。同时,它还考虑了继承。
二、在许多情况下,可能都需要进行此类检查。例如,它可以被用来构建一个多态性(polymorphic)的函数,该函数根据参数的类型对参数进行不同的处理。
三、instanceof 语法:
obj instanceof Class
1、如果obj隶属于Class类(或Class类的衍生类),则返回true。
【示例1】
class Rabbit {}
let rabbit = new Rabbit();
// rabbit 是 Rabbit class 的对象吗?
alert( rabbit instanceof Rabbit ); // true
【示例2】它还可以与构造函数一起使用
// 这里是构造函数,而不是 class
function Rabbit() {}
alert( new Rabbit() instanceof Rabbit ); // true
【示例3】与诸如Array之类的内建 class 一起使用:
let arr = [1, 2, 3];
alert( arr instanceof Array ); // true
alert( arr instanceof Object ); // true
2、有一点需要留意,arr同时还隶属于Object类。因为从原型上来讲,Array是继承自Object的。
四、通常,instanceof在检查中会将原型链考虑在内。此外,我们还可以在静态方法Symbol.hasInstance中设置自定义逻辑。
五、obj instanceof Class算法的执行过程大致如下:
1、如果这儿有静态方法Symbol.hasInstance,那就直接调用这个方法:
// 设置 instanceOf 检查
// 并假设具有 canEat 属性的都是 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) 被调用
2、大多数 class 没有Symbol.hasInstance。在这种情况下,标准的逻辑是:使用obj instanceOf Class检查Class.prototype是否等于obj的原型链中的原型之一。
(1)换句话说就是,一个接一个地比较:
obj.__proto__ === Class.prototype?
obj.__proto__.__proto__ === Class.prototype?
obj.__proto__.__proto__.__proto__ === Class.prototype?
...
// 如果任意一个的答案为 true,则返回 true
// 否则,如果我们已经检查到了原型链的尾端,则返回 false
六、在上面那个例子中,rabbit.proto === Rabbit.prototype,所以立即就给出了结果。
七、而在继承的例子中,匹配将在第二步进行:
class Animal {}
class Rabbit extends Animal {}
let rabbit = new Rabbit();
alert(rabbit instanceof Animal); // true
// rabbit.__proto__ === Rabbit.prototype
// rabbit.__proto__.__proto__ === Animal.prototype(匹配!)
八、下图展示了rabbit instanceof Animal的执行过程中,Animal.prototype是如何参与比较的:
九、这里还要提到一个方法objA.isPrototypeOf(objB),如果objA处在objB的原型链中,则返回true。所以,可以将obj instanceof Class检查改为Class.prototype.isPrototypeOf(obj)。
1、但是Class的 constructor 自身是不参与检查的!检查过程只和原型链以及Class.prototype有关。
十、创建对象后,如果更改prototype属性,可能会导致有趣的结果。
【示例1】
function Rabbit() {}
let rabbit = new Rabbit();
// 修改了 prototype
Rabbit.prototype = {};
// ...再也不是 rabbit 了!
alert( rabbit instanceof Rabbit ); // false
使用 Object.prototype.toString 方法来揭示类型
一、大家都知道,一个普通对象被转化为字符串时为[object Object]:
let obj = {};
alert(obj); // [object Object]
alert(obj.toString()); // 同上
二、这是通过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 = [];
alert( objectToString.call(arr) ); // [object Array]
五、这里我们用到了(见装饰器模式和转发,call/apply :https://www.yuque.com/tqpuuk/yrrefz/btx73p)中的call方法来在上下文this=arr中执行函数objectToString。
六、在内部,toString的算法会检查this,并返回相应的结果。
【示例1】再举几个例子:
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自定义对象的toString方法的行为。
【示例1】
let user = {
[Symbol.toStringTag]: "User"
};
alert( {}.toString.call(user) ); // [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。