JS怎么准确判断数据类型?

4,348 阅读3分钟

原文链接

前言

ES5 中有五种基本(原始)数据类型undefinednullbooleannumberstring,ES6 中新增了一种基本数据类型:Symboltypeof是我们开发中最常用的判断数据类型的JS原生内置运算符,但是有局限性。

typeof 运算符

语法:

typeof运算符后跟操作数:

typeof ${操作数}
// or
typeof (${操作数})

示例:

typeof(undefined); // undefined
typeof(null); // object
typeof(true); // boolean
typeof(1); // number
typeof(''); // string
typeof(Symbol(1)); // symbol
typeof(function () {}); // function
typeof([]); // object
typeof({}); // object
typeof(new Date()); // object
typeof(/abc/ig);  // object
typeof(Math);  // object
typeof(new Error('error')); // object

这里有两点需要注意的:

  • typeof null将返回object。因为在 JS 的最初版本中,使用的是 32 位系统,为了性能考虑使用低位存储了变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object,然后被 ECMAScript 沿用了 。
  • typeof不能准确判别对象类型究竟是什么具体对象。例如typeof {}typeof new Date(), typeof /abc/igtypeof Math,都是返回object,有没有可能告诉我们这是一个date对象,那是一个regexp对象呢?。还有一个不能忍受的是,typeof []也是返回object。很多时候,我们业务中希望能准确区分是array还是object

另外,instanceof 也可以判断对象类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype。但是,并不适用于一些基本数据类型。

1 instanceof Number; // false
var num = new Number(1);
num instanceof Number; // true

思考

既然typeofinstanceof都有局限性,那么有没有一种相对准确的方法来判断数据类型呢?答案是肯定的,它就是Object.prototype.toString.call(xxx)方法,其结果返回格式形如:[object Array][object RegExp][object Date]等。我们可以根据其表达式的返回结果中的中括号中的第二个单词,就能准确判别这个数据的具体类型。网上已有很多资料介绍这个函数的用法,它的表现形式也有很多种:

1. Object.prototype.toString.call(xxx);
2. ({}).toString.call(xxx);
3. [].toString.call(xxx);
...

其实,写法再多也是万变不离其。都是调用了原型链上的原生toString方法,来为数据类型做强制类型转化。

实践

场景一

如果我们只需要准确判断六种基本数据类型,同时又能够准确区分数据类型是functionarray、还是object就足够的话,那么我们可以这样实现:

var superTypeof = function (val) {
    var ans = typeof val;
    if (ans === 'object') {
        if (val === null) {
            ans = 'null';
        } else if (Array.isArray(val)) {
            ans = 'array';
        }
    }
    return ans;
}

ps: 如果有兼容性要求的同学,可以将Array.isArray(val)语句,改成val instanceof Array

测试

superTypeof(undefined); // undefined
superTypeof(null); // null
superTypeof(true); // boolean
superTypeof(1); // number
superTypeof(''); // string
superTypeof(Symbol(1)); // symbol
superTypeof(function () {}); // function
superTypeof([]); // array
superTypeof({}); // object
superTypeof(new Date()); // object
superTypeof(/abc/ig); // object
superTypeof(Math); // object
superTypeof(new Error('error')); // object
... 

场景二

某一天,我们发现,以上的superTypeof函数,并不能准确告诉我们,返回的 Object 类型究竟是Date还是RegExp还是其他比较具体的对象。这个时候,我们就需要用到上述提及的Object.prototype.toString.call(xxx)方法了。

var superTypeof = function (val) {
    var ans = typeof val;
    if (ans === 'object') {
        ans = ({}).toString.call(val).slice(8,-1).toLowerCase();
    }
    return ans;
}

测试:

superTypeof(undefined); // undefined
superTypeof(null); // null
superTypeof(true); // boolean
superTypeof(1); // number
superTypeof(''); // string
superTypeof(Symbol(1)); // symbol
superTypeof(function () {}); // function
superTypeof([]); // array
superTypeof({}); // object
superTypeof(new Date()); // date
superTypeof(/abc/ig); // regexp
superTypeof(Math); // math
superTypeof(new Error('error')); // error
...

通过这种方式,我们就能准确判断JS中的数据类型了。

jQuery 实现方式

我们再来看看jquery是怎么实现类似的功能的:

var class2type = {},
    typeStr = "Boolean Number String Function Array Date RegExp Object Error Symbol";
typeStr.split(" ").forEach(function (item) {
    class2type[ "[object " + item+ "]" ] = item.toLowerCase();
});
var toType = function (obj) {
    if ( obj == null ) {
        return obj + "";
    }
    return typeof obj === "object" || typeof obj === "function" ?
		class2type[ toString.call( obj ) ] || "object" :
		typeof obj;
}

是不是觉得大同小异的实现方式,甚至还不够我写得优雅呢?其实不然,这有jQuery作者的用意。

最后,我想安利一个有类似功能,且强大精简的库typeof2