js框架开发之旅--函数式编程二

上一篇我们给我们的类库里添加了each方法。这一篇我将展示如何在each功能的基础上添加更多的方法。我们会参考Underscore和Prototype的一些方法,还有最近流行的对Array.prototype的扩展。


过滤器

过滤器让你在列表里删除不满足条件的元素:
turing.enumerable.filter([1, 2, 3, 4, 5, 6], function(n) { return n % 2 == 0; });
// 2,4,6
要实现过滤器我们要考虑的一些问题:
  • 检查有没有原生过滤器的实现
  • 否则使用turing.enumerable.each实现过滤器
  • 如果需要,可以把对象过滤到多维数组里
下面的测试用来检查数组和对象都可以进行过滤,我们上篇的测试也用来相同的方法:
Riot.context('turing.enumerable.js', function() {
  given('an array', function() {
    var a = [1, 2, 3, 4, 5];

    should('filter arrays', function() {
      return turing.enumerable.filter(a, function(n) { return n % 2 == 0; });
    }).equals([2, 4]);
  });

  given('an object', function() {
    var obj = { one: '1', two: '2', three: '3' };

    should('filter objects and return a multi-dimensional array', function() {
      return turing.enumerable.filter(obj, function(v, i) { return v < 2; })[0][0];
    }).equals('one');
  });
});

经过测试,数组和对象都能很好的被处理。Underscore也支持对象的过滤,但是和我们的结果却不一样(它只返回了值而不是键值对)。


元素检查

元素检查和过滤不同,因为detect不是一个ECMAScript方法,它用起来一样简单:
turing.enumerable.detect(['bob', 'sam', 'bill'], function(name) { return name === 'bob'; });
// bob
这类方法很有意思,因为他需要一个断点。你可能注意到each方法支持异常中断。
each: function(enumerable, callback, context) {
  try {
    // The very soul of each
  } catch(e) {
    if (e != turing.enumerable.Break) throw e;
  }

  return enumerable;
}
detect也使用了each方法,我们在回调函数里进行判断,如果条件成立就返回该值,然后抛出一个中断异常。
调用链
为了让我们的turing.js更好用,我们必须提供调用链的支持。一些类库在扩展Array.prototype时提供的链中的做法看上去似乎很自然,但是这样做破坏了我们保持命名空间和代码兼容性的原则。
我们单独给调用链的功能提供了一个接口,代码如下:
turing.enumerable.chain([1, 2, 3, 4]).filter(function(n) { return n % 2 == 0; }).map(function(n) { return n * 10; }).values();
我们通过each实现的方法,需要返回一个对象,可以继续调用另一个方法。如果你对代码不太理解,请看下面的注释:
.chain([1, 2, 3, 4])                         // 开始一个数组的调用链
.filter(function(n) { return n % 2 == 0; })  // 过滤掉数组中的奇数
.map(function(n) { return n * 10; })         // 让数组中的数都乘10
.values();                                   // 获取计算结果
//[20,40]
为了实现上述功能,我们需要一具有以下功能的类:
  • 保存一个临时变量
  • 然后把我们要处理的数据映射到临时变量
  • 运行完某个方法,返回this用来调用后面的方法
  • 所有方法调用完成后返回结果
如果使用闭包和apply来实现上述功能,就简单了:
// store temporary values in this.results
turing.enumerable.Chainer = turing.Class({
  initialize: function(values) {
    this.results = values;
  },

  values: function() {
    return this.results;
  }
});

// Map selected methods by wrapping them in a closure that returns this each time
turing.enumerable.each(['map', 'detect', 'filter'], function(methodName) {
  var method = turing.enumerable[methodName];
  turing.enumerable.Chainer.prototype[methodName] = function() {
    var args = Array.prototype.slice.call(arguments);
    args.unshift(this.results);
    this.results = method.apply(this, args);
    return this;
  }
});


结论

现在你应该知道如何处理下面的问题了:
  • 检查用来处理的集合的原生方法
  • 或者使用each来实现这些方法
  • 使用异常来实现中断
  • 使用闭包实现调用链
  • 把这个功能放在一个安全的命名空间里
如果你想为enumerable扩展更多的方法,你可以查看Underscore都实现了哪些方法,然后根据我们的规则,添加到对应的命名空间里面。

该篇以及以后的各篇都是主要讲代码的实现思路和接口设计,具体的代码请参考对应的代码库。本篇的代码参考turing.enumerable.js


牧客网--让自由职业变成一个靠谱的工作


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值