本文是underscore源码剖析系列的第二篇,主要介绍underscore中一些基础方法的实现。segmentfault
在上篇文章underscore总体架构分析中,咱们讲过_上面的方法有两种挂载方式,一个是挂载到_构造函数上以_.map(arr)的形式直接调用(在后文上统称构造函数调用),另外一种则是挂到_.prototype上以_(arr).map()的形式被实例调用(在后文上统称原型调用)。数组
翻一遍underscore源码你会发现underscore中的方法都是直接挂到_构造函数上实现的,可是会经过mixin方法来将_上面的方法扩展到_.prototype上面,这样这些方法既能够直接调用,又能够经过实例来调用。架构
_.mixin = function(obj) { // 遍历obj上全部的方法 _.each(_.functions(obj), function(name) { // 保存方法的引用 var func = _[name] = obj[name]; _.prototype[name] = function() { // 将一开始传入的值放到数组中 var args = [this._wrapped]; // 将方法的参数一块儿push到数组中(这里处理的很好,保证了func方法参数的顺序) push.apply(args, arguments); // 这里先用apply方法执行了func,并将结果传给了result return result(this, func.apply(_, args)); }; }); }; _.mixin(_);
从这段代码中咱们能够看出,mixin方法将_上的全部方法经过遍历的形式挂载到了_.prototype上面。app
细心观察一下,构造函数调用和原型调用的区别在哪里?
没错,区别就在于调用方式和传参,构造函数调用时通常会把要处理的值当作第一个参数传入,而原型调用的时候会把要处理的值传入_构造函数来建立一个实例。函数
var arr = [1, 2, 3] var func = function(item) { console.log(item); } // 构造函数调用时arr被传入第一个参数 _.each(arr, func) // 原型调用的时候,arr被当作参数传给_方法来建立一个实例 _(arr).each(func) // 链式调用,和上面相似 _.chain(arr).each(func)
从上一节中咱们知道,在建立一个_的实例时,会用this._wrapped将传入的值保存起来,因此在mixin里面这一句:var args = [this._wrapped];是将咱们传给_的值放到args数组第一项中,以后再将arguments也放入args数组中,借助apply方法执行当前遍历的方法(在这个例子中是each),这个时候传给each方法的是arr和func,正好和原来直接_.each调用each传入参数的顺序是同样的(underscore中的方法第一项基本上都是要处理的数据)。源码分析
那么上面最后return result(this, func.apply(_, args)),result又是作什么的呢?this
首先来看result源码:prototype
var result = function(instance, obj) { // 首先判断是否使用链式调用,若是是,那就继续将刚刚执行后返回的结果链式调用一下,若是不是,则直接返回执行后的结果 return instance._chain ? _(obj).chain() : obj; }; _.chain = function(obj) { // 建立一个实例 var instance = _(obj); // 给这个实例加个_chain属性来代表这是链式调用 instance._chain = true; return instance; };
咱们知道underscore中也是有和jQuery相似的链式调用,来看一下链式调用的例子:code
var arr = [1, 2, 3] var newArr = _.chain(a).map(function(item) { return item + 1 }).filter(function(item) { return item > 2 }).value()
链式调用的关键在于每次执行方法后都须要返回一个实例,以确保可以继续调用其余方法。
chain方法会用传入的obj建立一个_的实例,这个实例能够调用原型上的方法。从上面mixin的实现来看,每次调用原型方法后会将执行后的结果传给result方法,在result内部会判断你是否使用了链式调用(chain),若是是链式的,那么就会将返回结果链式化(传入chain中建立新的实例)。
链式调用必定要在结尾执行value方法,否则最后返回的是一个对象(最后一次建立的_实例)对象
underscore构造方法上面并无直接对push、pop、shift等数组方法进行实现,可是链式调用的时候每每须要用到这些方法,因此在原型上对这些方法作了一些封装,实现方法和mixin相似,这里再也不多作解释。
_.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { var obj = this._wrapped; method.apply(obj, arguments); // 这句是好像是为了解决ie上的bug? if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; return result(this, obj); }; }); _.each(['concat', 'join', 'slice'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { return result(this, method.apply(this._wrapped, arguments)); }; });