Underscore 源码学习 (六) - mixin

关于 Underscore 的 mixin 方法。
首先先看个例子:

function Panel() {
  consoe.log(this, this instanceof Panel);
  if(this instanceof Panel) {
    return this;
  } else {
    return new Panel();
  }
}
a = Panel();        // Window   false
b = new Panel();    // Panel{}  true

当函数作为构造器使用时,函数内的 this 执行被新建的对象。当函数被调用时,函数内的 this 则为被调用的对象,在这里是 Window

  var _ = function(obj) {
    if (obj instanceof _) return obj;
    if (!(this instanceof _)) return new _(obj);
    this._wrapped = obj;
  };

同样的,如果我们使用下面方法调用:

var under = _();

第二个条件成立,所以新建一个 _ 对象后返回,注意这里是再次调用这个函数。
如果我们这样调用:

var under = new _();

就好像上面第二次调用一样,这时候就构造了 under 这个对象,如果传入了参数 obj,则把 obj 存入 under 这个对象的 _wrapped 属性中。
Underscore 提供了一个 OO 的调用方法,即:

var chainResult = function(instance, obj) {
    return instance._chain ? _(obj).chain() : obj;
};

_.mixin = function(obj) {
    // 遍历obj中的函数
    _.each(_.functions(obj), function(name) {
        // 避免原型链查找,提升性能
        var func = _[name] = obj[name];
        _.prototype[name] = function() {
            // 把wrapped作为数组第一个参数(context),其余传参push到这个数组中
            var args = [this._wrapped];
            push.apply(args, arguments);
            // 如果this是一个_实例,则使用func调用的结果来新建_实例后返回以供继续链式调用
            // 如果this不是一个_实例,则直接返回func调用的结果
            return chainResult(this, func.apply(_, args));
        };
    });
    return _;
};

// 把Underscore对象mixin化,这样就可以直接在_上调用方法
_.mixin(_);

当然我们还可以把自己写的方法通过 mixin 加入到 Underscore 对象中。

在这段代码后面还把原生的一些操作方法也添加到这个 _ 上面,这样我们就可以直接在 _ 上调用这些方法。例如:

// Add all mutator Array functions to the wrapper.
_.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);
        if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
        return chainResult(this, obj);
    };
});

上面这些方法并不是 Underscore 新建的,不存在于 Underscore 对象的原型链上,所以我们要把他们加进去。和上面 mixin 方法类似,下面这段代码是为了兼容 IE 而采取的操作:

if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];

jdalton commented on 6 Dec 2011
IE bugs with splice() and shift(), failing to remove the 0 indexed value, when using an array-like-object with _(...).
IE compatibility mode and IE < 9 have buggy Array shift() and splice() functions that fail to remove the last element, object[0], of array-like-objects even though the length property is set to 0.

通过上面这些方法把 Underscore 转化为可面向对象编程,调用更加优雅,我们可以有以下两种使用方法:

_.map([1, 2, 3], function(n){ return n * 2; });
_([1, 2, 3]).map(function(n){ return n * 2; });

至于选择哪一种就看你的喜好了~