Javascript是一门动态类型的语言,一个变量从声明到最后使用,可能经过了很多个函数,而数据类型也会发生改变,那么,对一个变量的数据类型判断就显得尤为重要。
获取数据类型
我们先来看下怎么获取一个数据的类型。
typeof是否能正确判断类型?
由于由于历史原因,在判断原始类型时,typeof null
会等于object
。而且对于对象(Object)、数组(Array)来说,都会转换成object
。例子如下:
typeof 1; // 'number'
typeof "1"; // 'string'
typeof null; // 'object'
typeof undefined; // 'undefined'
typeof []; // 'object'
typeof {}; // 'object'
typeof function () {}; // 'function'
所以我们可以发现,typeof可以判断基本数据类型,但是难以判断除了函数以外的复杂数据类型。于是我们可以使用第二种方法,通常用来判断复杂数据类型,也可以用来判断基本数据类型。
对于返回值为object
,有三种情况:
- 值为null
- 值为object
- 值为array
对于null,我们可以直接用===来进行判断,那么数组和对象呢?不急,我们接着说。
instanceof是否能正确判断类型?
instanceof
是通过原型链来判断的,但是对于对象来说,Array
也会被转换成Object
,而且也不能区分基本类型string
和boolean
。可以左边放你要判断的内容,右边放类型来进行JS类型判断,只能用来判断复杂数据类型,因为instanceof 是用于检测构造函数(右边)的 prototype 属性是否出现在某个实例对象(左边)的原型链上。例如:
function Func() {}
const func = new Func();
console.log(func instanceof Func); // true
const obj = {};
const arr = [];
obj instanceof Object; // true
arr instanceof Object; // true
arr instanceof Array; // true
const str = "abc";
const str2 = new String("abc");
str instanceof String; // false
str2 instanceof String; // true
单独使用instanceof
好像也是不行的,但是我们对于typeof已经得出结论,不能区分数组和对象,那么,我们结合下instanceof
,来写一个完整的判断逻辑
function myTypeof(data) {
const type = typeof data;
if (data === null) {
return "null";
}
if (type !== "object") {
return type;
}
if (data instanceof Array) {
return "array";
}
return "object";
}
constructor
constructor 判断方法跟instanceof相似,但是constructor检测Object与instanceof不一样,constructor还可以处理基本数据类型的检测,不仅仅是对象类型。
注意:
- null和undefined没有constructor;
- 判断数字时使用(),比如 (123).constructor,如果写成123.constructor会报错
- constructor在类继承时会出错,因为Object被覆盖掉了,检测结果就不对了
function A() {}
function B() {}
A.prototype = new B();
console.log(A.constructor === B); // false
var C = new A();
console.log(C.constructor === B); // true
console.log(C.constructor === A); // false
C.constructor = A;
console.log(C.constructor === A); // true
console.log(C.constructor === B); // false
Array.isArray()
Array.isArray() 用于确定传递的值是否是一个 Array。如果对象是 Array ,则返回true,否则为false。
Array.isArray([1, 2, 3]); // true
Array.isArray({ foo: 123 }); // false
Array.isArray("foobar"); // false
Array.isArray(undefined); // false
正则判断
我们可以把对象和数组转成一个字符串,这样就可以做格式判断,从而得到最终的类型。
function myTypeof(data) {
const str = JSON.stringify(data);
if (/^{.*}$/.test(data)) {
return "object";
}
if (/^\[.*\]$/.test(data)) {
return "array";
}
}
Object.prototype.toString.call()
上面我们通过typeof
和instanceof
实现了一版类型判断,那么是否有其他渠道,使我们的代码更加简洁吗?答案就是使用Object.prototype.toString.call()
。
每个对象都有一个toString()
方法,当要将对象表示为文本值或以预期字符串的方式引用对象时,会自动调用该方法。默认情况下,从Object
派生的每个对象都会继承toString()
方法。如果此方法未在自定义对象中被覆盖,则toString()
返回[Object type]
,其中type
是对象类型。所以就有以下例子:
Object.prototype.toString.call(new Date()); // [object Date]
Object.prototype.toString.call("1"); // [object String]
Object.prototype.toString.call(1); // [object Numer]
Object.prototype.toString.call(undefined); // [object Undefined]
Object.prototype.toString.call(null); // [object Null]
所以综合上述知识点,我们可以封装出以下通用类型判断方法:
function myTypeof(data) {
var toString = Object.prototype.toString;
var dataType =
data instanceof Element
? "Element"
: toString.call(data).replace(/\[object\s(.+)\]/, "$1");
return dataType;
}
myTypeof("a"); // String
myTypeof(1); // Number
myTypeof(window); // Window
myTypeof(document.querySelector("h1")); // Element
获取实例化对象的类名
题目中的第二个要求,是对于自定义的类实例化的对象,需要返回定义的类名。
这个也比较简单,我们对于上述获取的 Object 类型的数据,直接使用 xx.constructor.name
即可获取到这个数据对应的类名。
最终实现
function myTypeof(data) {
var toString = Object.prototype.toString;
var dataType =
data instanceof Element
? "Element"
: toString.call(data).replace(/\[object\s(.+)\]/, "$1");
if (dataType === "Object") {
return data.constructor.name;
}
return dataType;
}