JS 中有多个可以检测数据类型的方法,比如:
- typeof
- instanceof
- constructor
- Object.prototype.toString.call(instance) 但是它们基本都有一些不足,接下来就让我来一一说明一下
typeof
我们经常使用 typeof 判断数据的类型,其性能很高,但是它有两个致命的缺陷:
typeof null === 'object' // => true- 对
null使用typeof会返回object - 这是因为
typeof是浏览器底层基于二进制对值进行判断,对象类型是用 000 开头的二进制储存的,而null的二进制值是全为 0,因此它会被typeof判断为object
- 对
- 对除了
Function以外的原生对象类型使用typeof,其都只返回一个object
instanceof
a instanceof b 可以被认为判断在 a 的原型链上是否存在 b 的原型对象,可以简单的认为判断 a 是否为 b 的实例对象。
话虽如此,但是因为在 JS 中,我们可以随意改变原型的指向,因此此方法并不一定准确,比如
let fn = function () {};
fn.prototype = Array.prototype;
let f = new fn();
console.log(f instanceof Array); // => true
在上述代码中,fn 是一个函数,但是我们通过将其的原型对象指定为 Array 的原型对象,导致 Array 的原型对象出现在 fn 的实例的原型链上,因此返回了 true。
此外, instanceof 无法判断基本类型
在我们知道了 instanceof 的原理之后,我们可以自己实现一个 instanceof
function myInstanceof(instance, classFunc) {
let classFuncProto = classFunc.prototype; // 获取父类的原型
let proto = Object.getPrototype(proto); // 获取实例的原型
// 递归获取实例原型链上的原型
while (true) {
// 当实例原型链原型走到了 Object.prototype 即 null 时,
// 表示整个原型链都不存在父类原型
if (proto === null) return false;
if (proto === classFuncProto) return true;
proto = Object.getPrototype(proto);
}
}
constructor
正如我们所知,一个实例的 constructor 等于其构造函数的 constructor,因此我们可以根据这个原理来判断对象的数据类型。
但是,它也和 instanceof 一样,JavaScript 自身并不能确保正确的 constructor 函数值。它只存在于函数的默认 prototype 中,可以被随时改变
let fn = function () {};
let f = new fn();
f.constructor === fn; // => true
// 修改 fn 的构造函数指向
fn.prototype.constructor = Array;
f.constructor === fn; // => false
f.constructor === Array; // => true
Object.prototype.toString.call(instance)
这个方法可以说是究极方法了,这个方法用的是原型链最顶端的 toString 方法,通过调用 call 将 this 绑定到要检测的对象身上,会返回一个字符串"[Object ]",type 是判断出来的类型。
其返回的值可能是: [object Number/String/Boolean/Null/Undefined/Symbol/Object/Array/RegExp/Date/Function]
该方法的缺点在于代码太长,如果判断基本类型的话,我们也可以直接使用 typeof 来检测,因此我们可以考虑优化一下:
var class2type = {}
ve toString = class2type.toString
// 设置映射表
['Boolean','Number','String','Function','Array','Date','RegExp','Object','Error','Symbol'].forEach(name => {
class2type[`[object ${name}]`] =name.toLowerCase()
})
function toType(obj) {
if (obj == null) {
return obj + ''
}
return (typeof obj === 'object' || typeof obj === 'function') ? class2type[toString.call(obj)] : typeof obj
}