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       //判斷是否是類數組,一般不會傳入類似 {length: 3} 這樣的數據
 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   };

類數組,即擁有 length 屬性並且 length 屬性值為 Number 類型的元素,例如數組、arguments、HTMLCollection 以及 NodeList 等等,當然 {length: 3} 這種對象也滿足條件,但是_.each一般不會傳這種值。

這種判斷類數組的方法還是可以學習借鑒一下的。

 
接下來我們來聊上面提到的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操作符不僅在對象本身里查找,還會在原型鏈中查找。_.keys上增加了_.has()判斷,將原型上的過濾。
 
其中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   //跟_.extend相似,只是當key相同,只會去第一次的鍵值對,不會被后面的覆蓋
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,那么這里換句通俗的話說就是判斷key之前沒有出現過相同的值時,才進行拷貝處理,如果后面出現相同的key,將不再進行拷貝操作,只保存第一次的鍵值對結果。

_.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/1190000000531871
http://www.w3cfuns.com/house/17398/note/class/id/bb6dc3cabae6651b94f69bbd562ff370

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM