你的位置:首页 > Java教程

[Java教程]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   //获取所有的key5   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       //获取对应的keys12       //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    //如果两者值不等或者不在属性不在对象当中则返回false11    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