前言
ES5 中有五种基本(原始)数据类型undefined
,null
,boolean
,number
,string
,ES6 中新增了一种基本数据类型:Symbol
。typeof
是我们开发中最常用的判断数据类型的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/ig
,typeof Math
,都是返回object
,有没有可能告诉我们这是一个date
对象,那是一个regexp
对象呢?。还有一个不能忍受的是,typeof []
也是返回object
。很多时候,我们业务中希望能准确区分是array
还是object
。
另外,instanceof
也可以判断对象类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype。但是,并不适用于一些基本数据类型。
1 instanceof Number; // false
var num = new Number(1);
num instanceof Number; // true
思考
既然typeof
和instanceof
都有局限性,那么有没有一种相对准确的方法来判断数据类型呢?答案是肯定的,它就是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
方法,来为数据类型做强制类型转化。
实践
场景一
如果我们只需要准确判断六种基本数据类型,同时又能够准确区分数据类型是function
、array
、还是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。