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


牧客網--讓自由職業變成一個靠譜的工作


發佈了16 篇原創文章 · 獲贊 87 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章