上一篇我們給我們的類庫裏添加了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