你的位置:首页 > Java教程

[Java教程]underscore.js源码解析(五)—— 完结篇


最近公司各种上线,所以回家略感疲惫就懒得写了,这次我准备把剩下的所有方法全部分析完,可能篇幅过长...那么废话不多说让我们进入正题。

没看过前几篇的可以猛戳这里:

underscore.js源码解析(一)

underscore.js源码解析(二)

underscore.js源码解析(三)

underscore.js源码解析(四)

underscore.js源码GitHub地址: https://github.com/jashkenas/underscore/blob/master/underscore.js

本文解析的underscore.js版本是1.8.3

baseCreate

 1  var baseCreate = function(prototype) { 2   //判断参数是否是对象 3   if (!_.isObject(prototype)) return {}; 4   //如果有原生的就调用原生的 5   if (nativeCreate) return nativeCreate(prototype); 6   //继承原型 7   Ctor.prototype = prototype; 8   var result = new Ctor; 9   Ctor.prototype = null;10   return result;11  };

_.bind

1  _.bind = restArgs(function(func, context, args) {2   //如果不是函数抛异常3   if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');4   var bound = restArgs(function(callArgs) {5    //调用executeBound方法,具体解释见下方6    return executeBound(func, bound, context, this, args.concat(callArgs));7   });8   return bound;9  });

executeBound

 1  var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) { 2   //判断boundFunc 是否在callingContext 的原型链上 3   if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); 4   //创建实例 5   var self = baseCreate(sourceFunc.prototype); 6   //对实例进行apply操作 7   var result = sourceFunc.apply(self, args); 8   //如果是对象则返回对象 9   if (_.isObject(result)) return result;10   //否则返回实例本身11   return self;12  };

 _.partial

 1  _.partial = restArgs(function(func, boundArgs) { 2   //占位符 3   var placeholder = _.partial.placeholder; 4   var bound = function() { 5    var position = 0, length = boundArgs.length; 6    var args = Array(length); 7    //循环遍历boundArgs 8    for (var i = 0; i < length; i++) { 9     //判断是否是占位符,如果是就把arguments里的第一个放进去(按顺序以此类推),10     //如果不是占位符就正常把boundArgs里的数据再拷贝一份到args中11     args[i] = boundArgs[i] === placeholder ? arguments[position++] : boundArgs[i];12    }13    //循环遍历完boundArgs,就是把剩下的数据放入到args当中,这里调用executeBound,executeBound的分析可以看上面14    while (position < arguments.length) args.push(arguments[position++]);15    return executeBound(func, bound, this, this, args);16   };17   return bound;18  });

 _.bindAll

 1  _.bindAll = restArgs(function(obj, keys) { 2   keys = flatten(keys, false, false); 3   var index = keys.length; 4   //如果没有 function names抛异常 5   if (index < 1) throw new Error('bindAll must be passed function names'); 6   while (index--) { 7    var key = keys[index]; 8    //调用bind方法进行绑定 9    obj[key] = _.bind(obj[key], obj);10   }11  });

 多个方法绑定到对象上

_.memoize

 1  _.memoize = function(func, hasher) { 2   var memoize = function(key) { 3    //缓存值 4    var cache = memoize.cache; 5    //是否使用hashFunction,如果使用就把hashFunction的返回值作为缓存的key值 6    var address = '' + (hasher ? hasher.apply(this, arguments) : key); 7    //如果没有就做一个缓存的操作 8    if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); 9    //最后返回缓存值10    return cache[address];11   };12   memoize.cache = {};13   return memoize;14  };

 作用是缓存函数的计算结果,再做里面有重复运算的情况优化效果比较明显

_.delay

1  _.delay = restArgs(function(func, wait, args) {2   return setTimeout(function() {3    return func.apply(null, args);4   }, wait);5  });

 就是对setTimeout的封装,一目了然就不做过多解释了

_.defer

1 _.defer = _.partial(_.delay, _, 1);

 就是让这段程序最后执行,也是调用setTimeout来实现的,这里“_”是函数参数的占位,1是时间1毫秒。不懂的可以去看看setTimeout的机制,如果这里再展开的话篇幅过长,有时间也可以写一篇setTimeout的文章

_.throttle

 1  _.throttle = function(func, wait, options) { 2   var timeout, context, args, result; 3   //previous是缓存的上一次执行的时间点,默认为0 4   var previous = 0; 5   //判断是否有配置参数 6   if (!options) options = {}; 7  8   var later = function() { 9    previous = options.leading === false ? 0 : _.now();10    //清除timeout11    timeout = null;12    //储存函数执行的结果13    result = func.apply(context, args);14    if (!timeout) context = args = null;15   };16 17   var throttled = function() {18    //当前时间19    var now = _.now();20    if (!previous && options.leading === false) previous = now;21    //wait是setTimeout延迟的时间22    var remaining = wait - (now - previous);23    context = this;24    args = arguments;25    if (remaining <= 0 || remaining > wait) {26     if (timeout) {27      clearTimeout(timeout);28      timeout = null;29     }30     //缓存当前时间31     previous = now;32     result = func.apply(context, args);33     if (!timeout) context = args = null;34    } else if (!timeout && options.trailing !== false) {35     //生成定时器36     timeout = setTimeout(later, remaining);37    }38    return result;39   };40   //清除操作41   throttled.cancel = function() {42    clearTimeout(timeout);43    previous = 0;44    timeout = context = args = null;45   };46 47   return throttled;48  };

 _.throttle的作用是控制函数的执行频率,第一次执行的时候previous默认为零,那么remaining就是负数,没有定时器,之后当remaining大于0时,启动定时器,当定时器的时间到的时候,执行定时器里面的函数,并且会请一次timeout,remaining此时大于零并且timeout为空,则进入else if再次生成一个setTimeout。remaining > wait也就意味着now < previous,这是为了规避用户改变系统是简单的情况,这时候需要清除timeout的操作。

_.debounce

 1  _.debounce = function(func, wait, immediate) { 2   var timeout, result; 3  4   var later = function(context, args) { 5    timeout = null; 6    if (args) result = func.apply(context, args); 7   }; 8  9   var debounced = restArgs(function(args) {10    //判断是否立即调用11    var callNow = immediate && !timeout;12    if (timeout) clearTimeout(timeout);13    if (callNow) {14     //如果立即调用则,立即执行函数15     timeout = setTimeout(later, wait);16     result = func.apply(this, args);17    } else if (!immediate) {18     //如果本次调用时,上一个定时器没有执行完,将再生成一个定时器19     timeout = _.delay(later, wait, this, args);20    }21 22    return result;23   });24 25   debounced.cancel = function() {26    clearTimeout(timeout);27    timeout = null;28   };29 30   return debounced;31  };

  _.debounce也是函数节流,但是与throttle不同的是debounce中两个函数的时间间隔不能小于wait,这样的话定时器就会被重新创建

_.wrap

1  _.wrap = function(func, wrapper) {2   return _.partial(wrapper, func);3  };

 作用就是把func当做参数传给wrapper执行,_.partial前文介绍过,就是给函数设置一些默认的参数

_.compose

 1  _.compose = function() { 2   var args = arguments; 3   var start = args.length - 1; 4   return function() { 5    var i = start; 6    var result = args[start].apply(this, arguments); 7    //从后往前调用 8    while (i--) result = args[i].call(this, result); 9    return result;10   };11  };

  _.compose的作用就是组合复合函数,结构就是从最后一个函数开始执行,然后返回结果给前一个函数调用,直到第一个。

_.after

1  _.after = function(times, func) {2   return function() {3    if (--times < 1) {4     return func.apply(this, arguments);5    }6   };7  };

原理很简单,就是只有调用到最后一次的时候才开始执行里面的函数

_.before

 1  _.before = function(times, func) { 2   var memo; 3   return function() { 4    if (--times > 0) { 5     //正常调用,记录返回值 6     memo = func.apply(this, arguments); 7    } 8    //最后一次调用时,清空fun 9    if (times <= 1) func = null;10    return memo;11   };12  };

_.before的作用是限制函数的调用次数,最后一次调用清空fun,返回上一次调用的结果

_.once

1  _.once = _.partial(_.before, 2);

_.once调用了_.before并且times参数为2,说明无论调用几次,只返回第一次的调用结果

_.mapObject

 1  _.mapObject = function(obj, iteratee, context) { 2   iteratee = cb(iteratee, context); 3   var keys = _.keys(obj), 4     length = keys.length, 5     results = {}; 6   for (var index = 0; index < length; index++) { 7    var currentKey = keys[index]; 8    results[currentKey] = iteratee(obj[currentKey], currentKey, obj); 9   }10   return results;11  };

_.mapObject跟map类似,只不过它最后返回的是对象

_.pairs

1  _.pairs = function(obj) {2   var keys = _.keys(obj);3   var length = keys.length;4   var pairs = Array(length);5   for (var i = 0; i < length; i++) {6    pairs[i] = [keys[i], obj[keys[i]]];7   }8   return pairs;9  };

_.pairs的结构也很简单,就是把对象转化为数组

_.invert

1  _.invert = function(obj) {2   var result = {};3   var keys = _.keys(obj);4   for (var i = 0, length = keys.length; i < length; i++) {5    result[obj[keys[i]]] = keys[i];6   }7   return result;8  };

结构也是很清晰,就是一个翻转对象的过程,将对象的键和值互换位置

_.functions

1  _.functions = _.methods = function(obj) {2   var names = [];3   for (var key in obj) {4    if (_.isFunction(obj[key])) names.push(key);5   }6   return names.sort();7  };

就是获取对象的所有方法名,然后存在数组当中

_.pick

 1  _.pick = restArgs(function(obj, keys) { 2   var result = {}, iteratee = keys[0]; 3   //如果没有传入obj,则返回空 4   if (obj == null) return result; 5   //判断keys参数里是否传的是函数 6   if (_.isFunction(iteratee)) { 7    如果是函数,则调用函数进行上下文this的绑定 8    if (keys.length > 1) iteratee = optimizeCb(iteratee, keys[1]); 9    keys = _.allKeys(obj);10   } else {11   //如果不是函数,则为所需的属性12    iteratee = keyInObj;13    keys = flatten(keys, false, false);14    obj = Object(obj);15   }16   for (var i = 0, length = keys.length; i < length; i++) {17    var key = keys[i];18    var value = obj[key];19    if (iteratee(value, key, obj)) result[key] = value;20   }21   return result;22  });

作用是过滤出所需的键值对,对参数是属性的和函数的情况分别处理

_.omit

 1  _.omit = restArgs(function(obj, keys) { 2   var iteratee = keys[0], context; 3   if (_.isFunction(iteratee)) { 4    //这里一个取反的操作 5    iteratee = _.negate(iteratee); 6    if (keys.length > 1) context = keys[1]; 7   } else { 8    keys = _.map(flatten(keys, false, false), String); 9    iteratee = function(value, key) {10     //不存在的情况返回true11     return !_.contains(keys, key);12    };13   }14   //最后调用pick()15   return _.pick(obj, iteratee, context);16  });

_.omit相比较_.pick是一种相反的操作,作用是保留标记以外的对象

_.create

1  _.create = function(prototype, props) {2   //继承原型3   var result = baseCreate(prototype);4   //属性拷贝的操作5   if (props) _.extendOwn(result, props);6   return result;7  };

模拟Object.create方法

_.clone

1  _.clone = function(obj) {2   if (!_.isObject(obj)) return obj;3   return _.isArray(obj) ? obj.slice() : _.extend({}, obj);4  };

对象浅拷贝,如果是数组就调用slice,不是数组就调用_.extend

_.isMatch

 1  _.isMatch = function(object, attrs) { 2   var keys = _.keys(attrs), length = keys.length; 3   if (object == null) return !length; 4   //防止不是对象 5   var obj = Object(object); 6   for (var i = 0; i < length; i++) { 7    var key = keys[i]; 8    //如果对象属性不在obj中或者不在obj中 9    if (attrs[key] !== obj[key] || !(key in obj)) return false;10   }11   return true;12  };

  判断后者的对象属性是否全在前者的对象当中

eq

 eq = function(a, b, aStack, bStack) {  //虽然0 === -0成立,但是1 / 0 == 1 / -0 是不成立的,因为1 / 0值为Infinity, 1 / -0值为-Infinity, 而Infinity不等于-Infinity  if (a === b) return a !== 0 || 1 / a === 1 / b;  //null == undefined  if (a == null || b == null) return a === b;  //对NaN情况的判断,因为NaN!=NaN,所以a !== a说明a是NaN,如果b !== b为true,那么说明b是NaN,a和b相等,b !== b为false,说明b不是NaN,那么a和b不等  if (a !== a) return b !== b;  var type = typeof a;  if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;  return deepEq(a, b, aStack, bStack); };

deepEq

 1  deepEq = function(a, b, aStack, bStack) { 2   // 如果是underscore封装的对象,则通过_.wrapped中获取本身数据再进行对比 3   if (a instanceof _) a = a._wrapped; 4   if (b instanceof _) b = b._wrapped; 5   // 对两者的数据类型进行比较 6   var className = toString.call(a); 7   if (className !== toString.call(b)) return false; 8   switch (className) { 9    case '[object RegExp]':10    case '[object String]':11     // 正则转化字符串12     return '' + a === '' + b;13    case '[object Number]':14     // 对NaN情况的判断,跟eq里的判断一样,只不过多了转化数字这一步15     if (+a !== +a) return +b !== +b;16     // 如果不是NaN,那就要判断0的情况了,也是跟eq里面的判断同理17     return +a === 0 ? 1 / +a === 1 / b : +a === +b;18    case '[object Date]':19    case '[object Boolean]':20     //日期和布尔值转化为数字来比较,日期转化为数字是毫秒数21     return +a === +b;22   }23 24   var areArrays = className === '[object Array]';25   if (!areArrays) {26    //如果不是数组,只要有一个不是object类型就不等27    if (typeof a != 'object' || typeof b != 'object') return false;28 29    var aCtor = a.constructor, bCtor = b.constructor;30    //不同的构造函数是不等的,不同frames的object和Array是相等的31    if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor &&32                _.isFunction(bCtor) && bCtor instanceof bCtor)33              && ('constructor' in a && 'constructor' in b)) {34     return false;35    }36   }37  38   aStack = aStack || [];39   bStack = bStack || [];40   var length = aStack.length;41   while (length--) {42    // 对嵌套结构的做判断43    if (aStack[length] === a) return bStack[length] === b;44   }45 46   // 将a和b放入栈中47   aStack.push(a);48   bStack.push(b);49 50   // 对数组的判断处理51   if (areArrays) {52    length = a.length;53    //如果长度不等,那么肯定不等54    if (length !== b.length) return false;55    // 递归比较每一个元素56    while (length--) {57     if (!eq(a[length], b[length], aStack, bStack)) return false;58    }59   } else {60    //如果是对象61    var keys = _.keys(a), key;62    length = keys.length;63    // 相比较亮两个对象的属性数量是否相等64    if (_.keys(b).length !== length) return false;65    while (length--) {66     //递归比较每个属性是否相等67     key = keys[length];68     if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false;69    }70   }71   // 移除栈里的元素72   aStack.pop();73   bStack.pop();74   return true;75  };

_.isEmpty

1  _.isEmpty = function(obj) {2   if (obj == null) return true;3   if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0;4   return _.keys(obj).length === 0;5  };

就是一个判断为空的函数,结构很简单

_.isElement

1  _.isElement = function(obj) {2   return !!(obj && obj.nodeType === 1);3  };

判断是都是DOM元素

_.times

1  _.times = function(n, iteratee, context) {2   var accum = Array(Math.max(0, n));3   iteratee = optimizeCb(iteratee, context, 1);4   for (var i = 0; i < n; i++) accum[i] = iteratee(i);5   return accum;6  };

调用迭代函数n次,最后结果返回一个数组

_.template

 1  _.template = function(text, settings, oldSettings) { 2   //如果没有第二个参数,就将第三个参数赋值给第二个 3   if (!settings && oldSettings) settings = oldSettings; 4   //这里_.default函数前面介绍过填充属性为undefined的属性 5   settings = _.defaults({}, settings, _.templateSettings); 6  7   // 定义正则表达式,将settings里面的三个正则组合在一起,这里'|$'是为了让replace里面的函数多执行一遍 8   var matcher = RegExp([ 9    (settings.escape || noMatch).source,10    (settings.interpolate || noMatch).source,11    (settings.evaluate || noMatch).source12   ].join('|') + '|$', 'g');13 14   var index = 0;15   var source = "__p+='";16   //拼接字符串17   text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {18    source += text.slice(index, offset).replace(escapeRegExp, escapeChar);19    index = offset + match.length;20    //针对不同的情况进行拼接21    if (escape) {22     source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";23    } else if (interpolate) {24     source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";25    } else if (evaluate) {26     source += "';\n" + evaluate + "\n__p+='";27    }28 29    return match;30   });31   //下面是一个模板预编译的处理,主要用于调试32   source += "';\n";33 34   if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';35 36   source = "var __t,__p='',__j=Array.prototype.join," +37    "print=function(){__p+=__j.call(arguments,'');};\n" +38    source + 'return __p;\n';39 40   var render;41   try {42    render = new Function(settings.variable || 'obj', '_', source);43   } catch (e) {44    e.source = source;45    throw e;46   }47   //创建templete函数48   var template = function(data) {49    return render.call(this, data, _);50   };51   //设置source属性52   var argument = settings.variable || 'obj';53   template.source = 'function(' + argument + '){\n' + source + '}';54 55   return template;56  };

_.template就是一个模板函数,核心的部分还是前半段字符串拼接的过程(17-38行)。

首先先解释一下参数text是模板字符串,settings是正则匹配的规则,escape、interpolate、evaluate和variable

1  _.templateSettings = {2   evaluate: /<%([\s\S]+?)%>/g,3   interpolate: /<%=([\s\S]+?)%>/g,4   escape: /<%-([\s\S]+?)%>/g5  };

evaluate是用来执行任意的JavaScript代码,interpolate是用来插入变量的,escape是HTML转义的

里面有个noMatch,他是为了避免settings中缺少属性的情况

1 var noMatch = /(.)^/;

17行里offset是用来记录匹配当前位置的,剩下的主要就是走21-27行了,插入值就走23-24行判断,如果遇到一些需要js判断转换的数据就走25-26行判断,最后匹配$也是为了再执行一遍将最后面的html拼接进字符串。

其中18行中的escapeRegExp和escapeChar就是用来转化一些特殊字符的

 1  var escapes = { 2   "'": "'", 3   '\\': '\\', 4   '\r': 'r', 5   '\n': 'n', 6   '\u2028': 'u2028', 7   '\u2029': 'u2029' 8  }; 9 10  var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g;11 12  var escapeChar = function(match) {13   return '\\' + escapes[match];14  };

小结

到这里underscore.js就都分析完了,断断续续用了一个月的时间,在读这些函数方法的时候收获很多,也发现了一些以前自己理解不全面的地方,对自己也是个检验,就拿上面的template来说,就发现了自己正则方面的掌握还不够,在分析方法时学习了一些这些好的编程思想。

下一篇可能是对定时器的分析,到时候再看自己的研究效果吧。

夜已深,去睡觉了...

感谢大家的观看,也希望能够和大家互相交流学习,有什么分析的不对的地方欢迎大家批评指出

参考资料

http://www.w3cfuns.com/house/17398/note/class/id/bb6dc3cabae6651b94f69bbd562ff370