underscore源码分析之基础方法

underscore源码分析之基础方法


本文是underscore源码剖析系列的第二篇,主要介绍underscore中一些基础方法的实现。segmentfault

mixin

在上篇文章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));
    };
});
相关文章
相关标签/搜索