針對underscore.js封裝的方法進行具體的分析
前幾天我對underscore.js的整體結構做了分析,今天我將針對underscore封裝的方法進行具體的分析,代碼的一些解釋都寫在了註釋里,那麼廢話不多說進入今天的正文。
沒看過上一篇的可以猛戳這裡:underscore.js源碼解析(一)
underscore.js源碼GitHub地址: https://github.com/jashkenas/underscore/blob/master/underscore.js本文解析的underscore.js版本是1.8.3
_.each
1 _.each = _.forEach = function(obj, iteratee, context) { 2 //optimizeCb( )是underscore內部用來執行函數的很重要的方法,這個我們後面再聊 3 iteratee = optimizeCb(iteratee, context); 4 var i, length; 5 if (isArrayLike(obj)) { 6 //數組 7 for (i = 0, length = obj.length; i < length; i++) { 8 iteratee(obj[i], i, obj); 9 } 10 } else { 11 //對象處理,這個_.keys( )我們也後面再聊 12 var keys = _.keys(obj); 13 for (i = 0, length = keys.length; i < length; i++) { 14 iteratee(obj[keys[i]], keys[i], obj); 15 } 16 } 17 return obj; 18 };
_.each結構很清晰,如果是數組,就遍曆數組調用相應的處理方法,如果是對象的話,就遍歷對象調用相應的處理方法。
其中判斷是否為數組的代碼如下:
1 var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; 2 //獲取"length"屬性 3 var getLength = property('length'); 4 //判斷是否是數組 5 var isArrayLike = function(collection) { 6 var length = getLength(collection); 7 return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX; 8 };這種方法還是可以學習借鑒一下的。 接下來我們來聊上面提到的optimizeCb(),它是underscore內部用來執行函數的很重要的方法,並且改變所執行函數的作用域。
optimizeCb
1 var optimizeCb = function(func, context, argCount) { 2 if (context === void 0) return func; 3 //argCount為函數參數的個數,針對不同參數個數進行不同的處理 4 switch (argCount == null ? 3 : argCount) { 5 //為單值的情況,例如times函數 6 case 1: return function(value) { 7 return func.call(context, value); 8 }; 9 //因為2個參數的情況沒用被用到,所以在新版中被刪除了 10 //3個參數用於一些迭代器函數,例如map函數 11 case 3: return function(value, index, collection) { 12 return func.call(context, value, index, collection); 13 }; 14 // 4個參數用於reduce和reduceRight函數 15 case 4: return function(accumulator, value, index, collection) { 16 return func.call(context, accumulator, value, index, collection); 17 }; 18 } 19 return function() { 20 return func.apply(context, arguments); 21 }; 22 };
cb和_.iteratee
1 var cb = function(value, context, argCount) { 2 //如果為空,則返回value本身(identity函數就是一個返回本身的函數 ) 3 if (value == null) return _.identity; 4 //如果為函數,則改變所執行函數的作用域 5 if (_.isFunction(value)) return optimizeCb(value, context, argCount); 6 //如果是對象,判斷是否匹配(matcher是一個用來判斷是否匹配的,我們具體後續再聊) 7 if (_.isObject(value)) return _.matcher(value); 8 return _.property(value); 9 }; 10 // 通過調用cb函數,生成每個元素的回調 11 _.iteratee = function(value, context) { 12 return cb(value, context, Infinity); 13 };
_.keys
1 _.keys = function(obj) { 2 //如果不是對象,返回空數組 3 if (!_.isObject(obj)) return []; 4 //如果支持原生的方法,就調用原生的keys方法 5 if (nativeKeys) return nativeKeys(obj); 6 var keys = []; 7 //記錄所有屬性名 8 for (var key in obj) if (_.has(obj, key)) keys.push(key); 9 // IE9以下枚舉bug的相容處理 10 if (hasEnumBug) collectNonEnumProps(obj, keys); 11 return keys; 12 };獲取所有的屬性名存在數組當中。 這裡的in操作符不僅在對象本身里查找,還會在原型鏈中查找。 其中IE9以下枚舉bug相容處理源碼如下:
1 //判斷是否存在枚舉bug 2 var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString'); 3 //不可枚舉的屬性如下 4 var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; 5 6 var collectNonEnumProps = function(obj, keys) { 7 var nonEnumIdx = nonEnumerableProps.length; 8 var constructor = obj.constructor; 9 var proto = _.isFunction(constructor) && constructor.prototype || ObjProto; 10 11 // Constructor單獨處理部分. 12 var prop = 'constructor'; 13 如果對象和keys都存在constructor屬性,則把他存入keys數組當中 14 if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop); 15 16 while (nonEnumIdx--) { 17 prop = nonEnumerableProps[nonEnumIdx]; 18 //如果obj對象存在上面數組裡那些不可枚舉的屬性但是不在原型中,並且keys數組裡面也沒有的話 19 if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)){ 20 //將其添加進來 21 keys.push(prop); 22 } 23 } 24 };
既然都說到了keys那麼順帶著也介紹一下allkeys吧。
_.allKeys
1 _.allKeys = function(obj) { 2 if (!_.isObject(obj)) return []; 3 var keys = []; 4 //獲取所有的key 5 for (var key in obj) keys.push(key); 6 // 依然是IE9以下枚舉bug的相容處理 7 if (hasEnumBug) collectNonEnumProps(obj, keys); 8 return keys; 9 };
其實keys和allKeys代碼對比就少了if (_.has(obj, key)),allKeys是獲取所有的,包括繼承的
在介紹_.matcher之前,需要先介紹一下createAssigner 和_.isMatch。
createAssigner
1 var createAssigner = function(keysFunc, defaults) { 2 return function(obj) { 3 var length = arguments.length; 4 //判斷是否是對象 5 if (defaults) obj = Object(obj); 6 //如果一個參數或者對象為空,則返回這個對象 7 if (length < 2 || obj == null) return obj; 8 //從第二個參數開始 9 for (var index = 1; index < length; index++) { 10 var source = arguments[index], 11 //獲取對應的keys 12 //keysFunc只有keys和allKeys兩種,在下麵_.extend和_.extendOwn中可以看到 13 keys = keysFunc(source), 14 l = keys.length; 15 //進行拷貝處理 16 for (var i = 0; i < l; i++) { 17 var key = keys[i]; 18 //defaults是為了對defaults做單獨處理而添加的參數,具體的解釋_.defaults里做詳細分析 19 //在_.extend和_.extendOwn中default沒有傳值所以是underfinded,所以下麵判斷條件橫為true,正常進行拷貝處理 20 if (!defaults || obj[key] === void 0) obj[key] = source[key]; 21 } 22 } 23 return obj; 24 }; 25 };
1 //把後面的source拷貝到第一個對象 2 _.extend = createAssigner(_.allKeys); 3 4 //把後面的source拷貝到第一個對象 5 // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) 6 _.extendOwn = _.assign = createAssigner(_.keys); 7 8 //對象中屬性值為underfined的屬性進行拷貝填充 9 _.defaults = createAssigner(_.allKeys, true);把createAssigner中處理defaults的代碼拿到這裡具體分析
if (!defaults || obj[key] === void 0) obj[key] = source[key];
此時defaults參數為true,所以就要看obj[key] === void 0這句了,void 0返回的就是underfinded,那麼這句就是判斷這個對象中屬性值為underfinded時,進行拷貝處理。
_.isMatch
1 //用來判斷該屬性是否在對象中 (包括原型鏈) 2 _.isMatch = function(object, attrs) { 3 var keys = _.keys(attrs), length = keys.length; 4 //判斷對象是否為空 5 if (object == null) return !length; 6 //判斷是否是對象 7 var obj = Object(object); 8 for (var i = 0; i < length; i++) { 9 var key = keys[i]; 10 //如果兩者值不等或者不在屬性不在對象當中則返回false 11 if (attrs[key] !== obj[key] || !(key in obj)) return false; 12 } 13 return true; 14 };
_.matcher和_.matches
1 // 判斷對象是否匹配attrs的屬性 2 _.matcher = _.matches = function(attrs) { 3 //進行拷貝 4 attrs = _.extendOwn({}, attrs); 5 return function(obj) { 6 //用來判斷該屬性是否在對象中,上文有提及 7 return _.isMatch(obj, attrs); 8 }; 9 };
小結
今天介紹了underscore.js中部分封裝函數,其他的會在後面的文章繼續一一分析
感謝大家的觀看,也希望能夠和大家互相交流學習,有什麼分析的不對的地方歡迎大家批評指出
參考資料
https://segmentfault.com/a/1190000000531871http://www.w3cfuns.com/house/17398/note/class/id/bb6dc3cabae6651b94f69bbd562ff370