(生活的道路一旦选定,就要勇敢地走到底,决不回头。——左拉)
typeof
typeof是在javascript编码过程中最常用的判断数据类型的方法,常用来判断数字,字符串,布尔值和undefind
console.log(typeof 1); // number
console.log(typeof ''); // string
console.log(typeof true); // boolean
console.log(typeof false); // boolean
console.log(typeof undefined); //undefined
但不能用来判断复杂数据类型,比如以下案例
console.log(typeof []); // object
console.log(typeof {}); // object
console.log(typeof null); // object
这是因为javascript中的数据类型为两大类,原始值和对象值,其实也就是基础类型和引用类型。
基础类型有undefined,null,boolean,number,string
引用类型有array,object,date,regexp,function等。
每当使用typeof时,v8引擎会判断该数型的类型,如果是基础类型,则返回对应的类型字符串,如果是引用类型,则统一返回object。
需要注意的是typeof null也会返回object,这是js早期的bug一直没解决,正常情况下null就是null。
关于 typoef null等于object的说明
日常判断基本类型使用typeof的话,问题不大。但假如要判断某一个属性的类型是否是数组就做不到了,因为对于typeof来说,数组和对象都是object。并且在一些复杂的业务场景中,还要对类与类之间进行判断,比如A类是否属于B类等自定义对象的类型检查场景,typeof就更无法满足了。
instanceof
为了解决typeof上述中无法解决的业务场景,js还提供了instanceof来通过原型链和原型的比较来达到类型检查的目的。
console.log([] instanceof Array); // true
console.log({} instanceof Object); // true
class Student { }
const student = new Student();
class Car { }
const car = new Car();
class Train extends Car { }
const train = new Train();
console.log(student instanceof Car); // false
console.log(car instanceof Car); // true
console.log(train instanceof Car); // true
其原理还要从js的原型链和原型说起
原型和原型链
如果要自己实现一个instanceof,那么就是这样
const instanceof_custom = (A, B) => {
let L = A.__proto__;
const R = B.prototype;
while (true) {
if (L === null)
return false;
if (R === L) // 当 R 显式原型 严格等于 L隐式原型 时,返回true
return true;
L = L.__proto__;
}
};
通过代码可以看出,通过递归查找A原型链上的原型来对比B的原型是否相等来达到类型判断的目的,一直到原型链为null时终止。
虽然这样能判断数组和对象了,但又带来了新的问题,比如以下示例
console.log([] instanceof Object); // true
console.log({} instanceof Object); // true
console.log(new Number() instanceof Object); // true
console.log(new String() instanceof Object); // true
console.log(new Boolean() instanceof Object); // true
由于数组和对象的原型链上都有object,就导致他们在用原型链和原型进行对比的时候就会出现相等的情况。
Object.prototype.toString.call
为了解决上述问题,js又提供了Object.prototype.toString()方法。
在js中每一个类型都有着自己的toStirng方法,他们各自返回自己转换成字符串之后的结果,比如
const str = '123';
const number = 123;
const fun = () => { };
const boolean = true;
const array = [1, 2, 3];
const obj = {};
const cls = class { }
console.log(str.toString()); // '123'
console.log(number.toString()); // '123'
console.log(fun.toString()); // '() => { }'
console.log(boolean.toString()); // 'true'
console.log(array.toString()); // '1,2,3'
console.log(obj.toString()); // '[object Object]'
console.log(cls.toString()); // 'class { }'
Object.prototype.toString()能够返回"[object Type]",其中Type是对象类型。如果对象具有Symbol.toStringTag值为字符串的属性,则该值将用作Type. 许多内置对象(包括Map和Symbol)都有一个Symbol.toStringTag. 一些早于 ES6 的对象没有Symbol.toStringTag,但仍然有一个特殊的标签,他们的值与他们的类型相同,包含Array,Function,Error,Boolean,Number,String,Date,RegExp。
而如果我们给他们自定义Symbol.toStringTag属性的话,就业能够达到自定义标签的目的。
class ValidatorClass {
get [Symbol.toStringTag]() {
return 'Validator';
}
}
Object.prototype.toString.call(new ValidatorClass()); // "[object Validator]"
在这里我们先我们删除Array自身的toString方法,然后让它通过原型链来使用上层Object的toString方法来测试一下。
const array = [1, 2, 3];
console.log(array.toString()); // ‘1,2,3’
delete Array.prototype.toString;
console.log(array.toString()); // ‘[object Array]’
至此,解决了instanceof判断对象的问题,但我们在实际应用中最好不要删除原型方法,这样会给程序造成极大的隐患。
那么就可以用js中的call方法来改变this指向,替换Object内部属性值的目的。让Object的toString方法读取Array的特殊标签,那么就可以返回‘[object Array]’了
结论
综上所述,三种判断js类型的方法各有所长,typeof写法简单便于理解,instanceof善于判断自定义对象,Object.prototype.toString.call虽然写起来稍复杂,但除了自定义对象,其他的类型都可以进行判断
至于只用哪一种,还要结合业务场景来进行选择。