上一篇我们给我们的类库里添加了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用来调用后面的方法
- 所有方法调用完成后返回结果
// 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来实现这些方法
- 使用异常来实现中断
- 使用闭包实现调用链
- 把这个功能放在一个安全的命名空间里
该篇以及以后的各篇都是主要讲代码的实现思路和接口设计,具体的代码请参考对应的代码库。本篇的代码参考turing.enumerable.js