underscore 原始碼版本 1.8.2
起因
很多人向我推薦研究js,可以看看一些第三方js類庫的原始碼,而原始碼之中最好解讀也最簡短的就是underscore,它也是我平常比較喜歡的一個庫,因為它價效比高:體積小、能力強。開啟一看,才1000多行,試著讀了一下,確實很值得一看,所以對精彩部分做了一下整理。
閉包
整個函式在一個閉包中,避免汙染全域性變數。通過傳入this(其實就是window物件)來改變函式的作用域。和jquery的自執行函式其實是異曲同工之妙。這種傳入全域性變數的方式一方面有利於程式碼閱讀,另一方面方便壓縮。
underscore寫法:
1 2 3 |
(function(){ ... }.call(this)); |
jquery寫法:
1 2 3 |
(function(window, undefined) { ... })(window); |
原型賦值
1 |
var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; |
Array,Object,Function這些本質都是函式,獲取函式原型屬性prototype也是為了便於壓縮。簡單解釋一下,如果程式碼中要擴充套件屬性,可能這樣寫
1 |
Object.prototype.xxx = ... |
而這種程式碼是不可壓縮的,Object
,prototype
這些名字改了瀏覽器就不認得了。
但是上面的程式碼中建立了ObjProto
之後,源生程式碼經過壓縮之後,ObjProto
就可能命名成a變數,那麼原來的程式碼就壓縮成
1 |
a.xxx = ... |
一個小建議就是凡事一段程式碼被使用兩次以上都建議定義變數(函式),有利於修改和壓縮程式碼。
格式
1 2 3 4 |
var nativeIsArray = Array.isArray, nativeKeys = Object.keys, nativeBind = FuncProto.bind, nativeCreate = Object.create; |
這種定義的方式省略了多餘的var,格式也美觀,讓我想到了sublime中的一個外掛alignment。
資料判斷
1 2 3 |
_.isElement = function(obj) { return !!(obj && obj.nodeType === 1); }; |
判斷是否為dom,dom的nodeType屬性值為1。這裡用!!
強轉為boolean值
1 2 3 |
_.isArray = nativeIsArray || function(obj) { return toString.call(obj) === '[object Array]'; }; |
判斷是否為陣列。由於Array.isArray函式是ECMAScript 5新增函式,所以為了相容之前的版本,在原生判斷函式不存在的情況下,後面重寫了一個判斷函式。用call函式來改變作用域可以避免當obj沒有toString函式報錯的情況。
1 2 3 4 |
_.isObject = function(obj) { var type = typeof obj; return type === 'function' || type === 'object' && !!obj; }; |
判斷是否為物件。先用typeof判斷資料型別。函式也屬於物件,但是由於typeof null也是object,所以用!!obj來區分這種情況。
1 2 3 4 5 |
if (!_.isArguments(arguments)) { _.isArguments = function(obj) { return _.has(obj, 'callee'); }; } |
判斷是否為arguments,很簡單,arguments有個特有屬性callee。
1 2 3 |
_.isNaN = function(obj) { return _.isNumber(obj) && obj !== +obj; }; |
NaN這個值有兩個特點:1.它是一個數;2.不等於它自己。
‘+’放在變數前面一般作用是把後面的變數變成一個數,在這裡已經判斷為一個數仍加上’+’,是為了把var num = new Number()
這種沒有值的數字也歸為NaN。
1 2 3 |
_.isBoolean = function(obj) { return obj === true || obj === false || toString.call(obj) === '[object Boolean]'; }; |
是不是以為如果是布林值不是true就是false?還有第3中情況var b = new Boolean()
。b也是布林值。
1 2 3 |
_.isUndefined = function(obj) { return obj === void 0; }; |
用void 0來表示undefined,非常有意思的小技巧。不過常用方式還是if(xxx)來判斷是不是undefined。
eq
是underscore的一個內建函式,程式碼太長,不貼上了。isEmpty呼叫了這個函式。整個思路由易到難,先用===比較簡單資料,然後用toString來判斷是否相等,最後用遞迴處理複雜的Array、Function和Object物件。
1 |
if (a === b) return a !== 0 || 1 / a === 1 / b; |
這裡為了區分’+0’和’-0’,因為這兩個數對計算結果是有影響的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
var className = toString.call(a); if (className !== toString.call(b)) return false; switch (className) { // Strings, numbers, regular expressions, dates, and booleans are compared by value. case '[object RegExp]': // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i') case '[object String]': // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is // equivalent to `new String("5")`. return '' + a === '' + b; case '[object Number]': // `NaN`s are equivalent, but non-reflexive. // Object(NaN) is equivalent to NaN if (+a !== +a) return +b !== +b; // An `egal` comparison is performed for other numeric values. return +a === 0 ? 1 / +a === 1 / b : +a === +b; case '[object Date]': case '[object Boolean]': // Coerce dates and booleans to numeric primitive values. Dates are compared by their // millisecond representations. Note that invalid dates with millisecond representations // of `NaN` are not equivalent. return +a === +b; } |
這裡是對簡單物件進行判斷,分為兩類,一類是String
和RegExp
,這種資料直接toString
然後判斷。另一類是Number
、Date
和Boolean
,通過轉換成數字判斷。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
aStack.push(a); bStack.push(b); if (areArrays) { length = a.length; if (length !== b.length) return false; while (length--) { if (!eq(a[length], b[length], aStack, bStack)) return false; } } else { var keys = _.keys(a), key; length = keys.length; if (_.keys(b).length !== length) return false; while (length--) { key = keys[length]; if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false; } } aStack.pop(); bStack.pop(); |
對於陣列和物件只能用遞迴了,同時用aStack和bStack來暫存遞迴中的子物件。這裡一個小技巧的就是先判斷陣列/屬性的長度,如果不相等可以有效地減少遞迴。
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式