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

1
2
3
4
5
6
7
8
9
10
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

1
2
3
4
5
var _ = function(obj) {
if (obj instanceof _) return obj;
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};

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

1
var under = _();

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

1
var under = new _();

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 对象中。

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

1
2
3
4
5
6
7
8
9
10
// 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 而采取的操作:

1
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 转化为可面向对象编程,调用更加优雅,我们可以有以下两种使用方法:

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

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