1. 引言
underscore.js是一个1500行左右的Javascript函数式工具库,里面提供了很多实用的、耦合度极低的函数,用来方便的操作Javascript中的数组、对象和函数,它支持函数式和面向对象链式的编程风格,还提供了一个精巧的模板引擎。理解underscore.js的源码和思想,不管是新手,还是工作了一段时间的人,都会上升一个巨大的台阶。虽然我不搞前端,但是在一个星期的阅读分析过程中,仍然受益匪浅,决定把一些自认为很有意义的部分记录下来。
2. 类型判断
在underscore.js里面,有大量的is前缀的函数:isEmpty
isElement
isArray
isObject
isArguments
isFunction
isString
isNumber
isDate
isRegExp
isError
isFinite
isNaN
isBoolean
isNull
isUndefined
搞定这些函数,可以对js的类型有一个非常深刻的理解,还可以学到很多的类型判断实用技巧。
先从软柿子开始捏:
isUndefined
_.isUndefined = function(obj) {
return obj === void 0;
};
这里出现了第一个技巧,void 0。void永远只会返回undefined,用void 0表示undefined比直接使用undefined更加准确,因为undefined并不是关键字,可以用作标识符:
<script>
window.onload = function() {
var undefined = 'a';
var b = undefined;
console.log(b);//输出为 a
};
</script>
isNull
_.isNull = function(obj) {
return obj === null;//必须要使用严格相等,因为null == undefined返回true
};
isBoolean
_.isBoolean = function(obj) {
return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
};
toString.call()是判断类型的最精确方式,这里先判断为true还是false是为了提高效率。
isArguments isFunction isString isNumber isDate isRegExp isError
_.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) {
_['is' + name] = function(obj) {
return toString.call(obj) === '[object ' + name + ']';
};
});
toString.call()是判断类型的最精确方式最通用的方式,这里动态构造了几个underscore的is函数。
isNaN
_.isNaN = function(obj) {
return _.isNumber(obj) && obj !== +obj;//NaN !== NaN
};
又出现一个技巧+,作用是将后面的变量转换成数字,如果无法转换则为NaN。肯定还记得坑爹的+[]
吧。
isFinite
_.isFinite = function(obj) {
return isFinite(obj) && !isNaN(parseFloat(obj));//先调用原生的isFinite,实际上isFinite(NaN)返回的就是false,后面的进一步判断应该是一些兼容措施。
};
isObject
_.isObject = function(obj) {
var type = typeof obj;
return type === 'function' || type === 'object' && !!obj;
};
typeof的判断比较粗糙,object代表很多种类型,null也是object,这里需要排除为null的情况,使用了一个技巧 !!,将其转换成对应的布尔类型。
isArray
_.isArray = nativeIsArray || function(obj) {
return toString.call(obj) === '[object Array]';//toString.call简单粗暴
};
||逻辑或经常用来做polyfill,静态语言的逻辑或只能返回true or false,而js的逻辑或可以返回表达式的值,经常用来消除if else
isElement
_.isElement = function(obj) {
return !!(obj && obj.nodeType === 1);//元素结点type为1
};
isEmpty
_.isEmpty = function(obj) {
if (obj == null) return true;
//常见的类数组有Array,String,Arguments
if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj)))
return obj.length === 0;
return _.keys(obj).length === 0;//如果是{}类型,取其属性名的数组
};
3. DuckTyping
DuckTyping是动态语言实现多态的一种强有力的方式,只要多个对象有相同的属性名和函数名,就认为这几个对象是一个种类:
//属性访问器
var property = function(key) {
return function(obj) {
return obj == null ? void 0 : obj[key];
};
};
//length属性访问器
var getLength = property('length');
//类数组判断,只要有length属性就当成是类数组,在这个函数里String, Array, Arguments都被认为是一个collection
var isArrayLike = function(collection) {
var length = getLength(collection);
return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
};
4. 总结
- 使用void 0 表示undefined.
- +将变量转换成数字类型,无法转换则为NaN.
- !!将变量转换成对应的布尔类型.
- 使用||来消除不必要的if else.
- toString.call是通用的类型判断方法.
- DuckTyping.