underscore.js原始碼解析之型別判斷
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.