Stonelee's Blog

如无必要,勿增实体

Underscore源码研究之多种编程范式实现

分享到: 更多

Underscore提供了函数(functional)和对象(object-oriented)两种编程范式,使得其API调用既灵活又直观,可以说是整个类库架构中最为精华之处。

两种调用方式

函数式

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

对象式

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

源码分析

调用入口

1
2
3
4
5
6
7
8
var _ = function(obj) {
  //如果参数为_对象,说明已经实例化过了,所以直接返回
  if (obj instanceof _) return obj;
  //如果实例化时没有使用new,那么在这里包装一下,使得this指向该实例
  if (!(this instanceof _)) return new _(obj);
  //将obj保存在内部属性_wrapped中
  this._wrapped = obj;
};

具体功能实现没什么好说的,直接定义为_的方法,因此可以进行函数式调用。 需要注意的是第一个参数为obj,在对象式调用时会省掉。

1
2
3
4
5
6
7
8
9
_.map = function(obj, iterator, context) {
  var results = [];
  if (obj == null) return results;
  if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
  each(obj, function(value, index, list) {
    results[results.length] = iterator.call(context, value, index, list);
  });
  return results;
};

重头戏来了。将方法附加到_的prototype上,实现对象式调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//此方法暴露出来,供自定义扩展使用
_.mixin = function(obj) {
  each(_.functions(obj), function(name){
    //定义为_的方法,供函数式调用
    var func = _[name] = obj[name];
    //定义到prototype上,供对象式调用
    _.prototype[name] = function() {
      //将实例化时传递给构造函数的对象作为第一个参数
      var args = [this._wrapped];
      push.apply(args, arguments);
      //判断是否要链式调用,见下文分析
      return result.call(this, func.apply(_, args));
    };
  });
};
//将之前定义到_上的方法附加到_的prototype上,实现对象式调用
_.mixin(_);

链式调用

两种编程范式都可以实现链式调用,与普通调用的区别是需要使用chain来声明,最后使用value来取得最终结果。

函数式

1
2
3
4
5
_.chain([1, 2, 3]).map(function(n) {
  return n * 2;
}).reduce(function(memo, n) {
  return memo + n;
}).value();

对象式

1
2
3
4
5
_([1, 2, 3]).chain().map(function(n) {
  return n * 2;
}).reduce(function(memo, n) {
  return memo + n;
}).value();

链式调用实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//判断是否要链式调用
var result = function(obj) {
  return this._chain ? _(obj).chain() : obj;
};

//函数式链式调用
_.chain = function(obj) {
  return _(obj).chain();
};

_.extend(_.prototype, {

  // 标记为链式调用,这样返回值就为this而不是计算得到的结果
  chain: function() {
    this._chain = true;
    return this;
  },

  // 取得最终的结果
  value: function() {
    return this._wrapped;
  }

});

链式调用的调试

链式调用非常方便,但是要调试中间步骤的结果比较困难,因此提供了tap方法

1
2
3
4
5
6
7
_([1, 2, 3]).chain().map(function(n) {
  return n * 2;
}).tap(function(obj) {
  console.log(obj);
}).reduce(function(memo, n) {
  return memo + n;
}).value();

源码实现

将中间过程的obj作为回调函数的参数进行处理,最终依然将其返回

1
2
3
4
_.tap = function(obj, interceptor) {
  interceptor(obj);
  return obj;
};

Comments