首先我们回忆一下,在js中,我们有哪几种方式来判断js变量的数据类型呢?想必大家都想到了有 typeof
、 instanceof
和Object.prototype.toString.call()
这几种方式去判断类型,那么这几种有什么的区别呢?
判断js类型的常见方式
typeof
直接上代码吧,可以看到typeof
判断基本数据类型是没有问题的,但是在判断复杂数据类型时都会被判断为 ‘object’,在其中可以看到有两个特例,一个是基本数据类型null
会被判断为 ‘object’,这是为什么呢,这就是一个悠久 Bug
就不说了;另一个是复杂数据类型中的函数会被判断为’function’,说明函数是可以被正确识别到的。
所以typeof
判断数据类型并不够准确,存在一定的缺陷。
typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeof null // 'object'
typeof [] // 'object'
typeof {} // 'object'
typeof console // 'object'
typeof console.log // 'function'
复制代码
instanceof
instanceof
运算符用于通过查找原型链来检测某个变量是否为某个类型数据的实例。
可以看到instanceof
可以用来判断复杂数据类型,返回true
/false
,而对于基本数据类型是不能判断的,但是可以判断基本数据类型的包装类型,这也是instanceof
的缺陷了。
1 instanceof Number // false
new Number(1) instanceof Number // true
'1' instanceof String // false
new String('1') instanceof String // true
true instanceof Boolean // false
(Symbol() instanceof Symbol // false
console.log instanceof Function // true
[] instanceof Array // true
{} instanceof Object // true
复制代码
Object.prototype.toString.call()
前面两种方式各有缺陷,而这种方式就算是杀手锏了,什么类型都能正确判断。
每种引用类型都会直接或者间接继承自Object类型,因此它们都包含toString()函数。不同数据类型的toString()类型返回值也不一样,所以通过toString()函数可以准确判断值的类型。
Object.prototype.toString.call(1) // "[object Number]"
Object.prototype.toString.call('1') // "[object String]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(undefined) // "[object Undefined]"
Object.prototype.toString.call(Symbol()) // "[object Symbol]"
Object.prototype.toString.call(function(){}) // "[object Function]"
Object.prototype.toString.call([]) //"[object Array]"
Object.prototype.toString({}) // "[object Object]"
Object.prototype.toString.call(/123/g) //"[object RegExp]"
Object.prototype.toString.call(new Date()) //"[object Date]"
复制代码
了解了以上方式,那我们来看看underscore
里面到底是怎么实现的吧
underscore实现方式
按照顺序,首先我们来看如何判断对象?underscore 把类型为 function 和 object 的变量都算作对象,当然得除去 null。
function isObject(obj) {
var type = typeof obj;
return type === 'function' || type === 'object' && !!obj;
}
复制代码
判断一个null
function isNull(obj) {
return obj === null;
}
复制代码
判断一个undefined
function isUndefined(obj) {
// 为什么 void 0 而不是直接 undefined ,因为 undefined 不是保留字,可以重新赋值
// 用 void 0 代替 undefined 避免 undefined 被重
return obj === void 0;
}
复制代码
判断一个boolean
function isBoolean(obj) {
return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
}
复制代码
写了这么多类型,是不是要这样一个一个的写下去呢,其他类型还是用typeof
、 instanceof
和特殊值嘛,当然不是,记下来Object.prototype.toString.call()
登场了,underscore
封装了一个函数tagTester
如下
var toString = Object.prototype.toString;
// 通过 原型链 判断
function tagTester(name) {
var tag = '[object ' + name + ']';
return function(obj) {
return toString.call(obj) === tag;
};
}
复制代码
有了上面的函数就好些其他类型的函数,一下子就多了好多函数了
var isString = tagTester('String');
var isNumber = tagTester('Number');
var isDate = tagTester('Date');
var isRegExp = tagTester('RegExp');
var isError = tagTester('Error');
var isSymbol = tagTester('Symbol');
var isArrayBuffer = tagTester('ArrayBuffer');
var isFunction = tagTester('Function');
复制代码
看到这里是不是很纳闷,怎么没有判断数组的呢,原来isArray被单独拿了出来,那么如何判断一个数组呢
// ES5 原生方法, 如果浏览器支持, 则 underscore 中会优先使用
var nativeIsArray = Array.isArray;
var isArray = nativeIsArray || tagTester('Array');
复制代码
判断一个boolean
var _isNaN = isNaN // 原生的 isNaN
function isNaN$1(obj) {
// NaN 必须是一个数字类型,在判断是不是 NaN
return isNumber(obj) && _isNaN(obj);
}
复制代码
到这就结束了吗?当然不是,underscore
还对一些类型判断做了兼容性处理,那是那些呢,我们一起来看看吧。
var isFunction = tagTester('Function');
// Optimize `isFunction` if appropriate. Work around some `typeof` bugs in old
// v8, IE 11 (#1621), Safari 8 (#1929), and PhantomJS (#2236).
var nodelist = root.document && root.document.childNodes;
// _.isFunction 在 old v8, IE 11 和 Safari 8 下的兼容
// 没看懂这个if判断具体干嘛的,有没有大佬解释一下
// 猜测:typeof obj == 'function' 性能更好,如果 typeof在该环境下没有兼容性bug就用这种方式
// 否则用上面那种
if (typeof /./ != 'function'
&& typeof Int8Array != 'object'
&& typeof nodelist != 'function') {
isFunction = function(obj) {
return typeof obj == 'function' || false;
};
}
复制代码
var isArguments = tagTester('Arguments');
// Define a fallback version of the method in browsers (ahem, IE < 9), where
// there isn't any inspectable "Arguments" type.
// _.isArguments 方法在 IE < 9 下的兼容
// IE < 9 下对 arguments 调用 Object.prototype.toString.call 方法 结果是 [object Object]
// 而并非我们期望的 [object Arguments]。
// so 用是否含有 callee 属性来判断
(function() {
if (!isArguments(arguments)) {
isArguments = function(obj) {
return has$1(obj, 'callee');
};
}
}());
复制代码
就这样吗?当然不是,underscore
还实现了很多类型判断和兼容性处理,大家自己去看看吧,这里就不说了(其实是我太菜了没看懂),那不第一部分就到这里结束啦。