迭代器/枚舉器/生成器的前世此生

一切爲了抽象

一段代碼越抽象它的複用價值就越高,舉一個極端例子:javascript

function emptyAction(x) { return x }

上面這個函數就像數學裏面的 f(x) = x 同樣,與山河同在,與日月同輝!java

好比可使用它進行數組的複製:git

let a = [1, 2, 3]
let aDuplicate = a.map(emptyAction)

好比能夠玩我返回我本身,雖然我也不知道這有什麼用:github

let one = emptyAction(emptyAction)(emptyAction)(1)

因此,爲了避免把那些 shiiiiit 代碼再寫一遍一遍又一遍,就抽象吧!編程

遍歷高桌子,遍歷低板凳

假設任何一組數據都永遠使用 Array 存儲,那麼下面這段代碼也是一段複用價值極高的代碼:設計模式

for (let i = 0; i < a.length; i++) {
  const element = a[i];
  // do something
}

儘管它並不直觀,我纔不想管 i 是什麼 length 是什麼,以及 daaaaamn a[i] 又是什麼!我只想遍歷數組中的每個元素,給我數組中的元素,OK???數組

並且事實上咱們除了 Array,還有 Set、Map、LinkedList 以及 maaaaany kinds of Object,很不幸它們並不支持這樣的寫法,即便實現了這樣的寫法,最終性能也會 shiiiiit 同樣(試想對一個 LinkedList 訪問第 i 個元素重複 n 次?)編程語言

來看看遍歷的一組數據的抽象描述,是這樣的:函數

const chocolates = anyObj.getChocolates() // 給我迭代器
while (chocolates.hasNext()) {
  const choco = chocolates.giveMeNext()
  // eat it
}

這個 chocolates 被人們稱做巧克力迭代器、巧克力枚舉器、巧克力生成器(誤),它在 20 世紀末被做爲一種設計模式提出,旨在讓每個類型按照本身內部組織數據的特色編寫具體的遍歷方法,調用方不須要知道具體的操做,只須要喊:「給我迭代器」,而後迭代就完事。性能

編程語言語法級別的支持

各家語言的發明者都嚐到了這種設計模式的香甜滋味,紛紛在發明語言之初,或者發佈語言的最新版本時爲這種設計模式提供了語法級別的支持!而且爲標準庫中各類對象(Array、Set、Map 等)實現了迭代器!Ohhhhh yeah!!!

JavaScript 爲這種設計模式提供的語法級支持包含:

  1. 使用迭代協議的循環語句 for...of
  2. 生成器函數的一系列語法 function* () { ...; yield xxx; ... yield* fff(); ... }

迭代協議

如下內容參考文檔:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols

先看一下 for...of 的實現原理

for (const element of anyObj) {
  // do something
}

上述代碼的等價代碼以下,看上去是稍微複雜了一些(否則爲何要提供 for...of 的語法呢)

{ // 新開一個做用域,生成的迭代器外部代碼不可見
  const chocolates = anyObj[Symbol.iterator]() // 返回一個全新的迭代器
  for (let chocoDesc = chocolates.next(); // 迭代出一個「巧克力描述」
    !chocoDesc.done; // 不是最後一塊巧克力
    chocoDesc = chocolates.next()) {
    const choco = chocoDesc.value // 得到巧克力
    // do something
  }
}

迭代協議能夠描述爲:

  • 一個對象擁有 Symbol.iterator 方法,且該方法返回——
    • 一個擁有 next 方法的對象,且 next 方法返回——
      • 一個擁有如下兩個屬性的對象——
        • done 爲表示迭代是否完成的布爾值
        • value 爲要遍歷的元素

實現了迭代協議的對象被稱做可迭代對象任何一個可迭代對象均可以使用 for...of 完成遍歷操做

如下是 LeetCode 中常見的鏈表類型實現迭代協議的例子:

function ListNode(val, next = null) {
  this.val = val
  this.next = next
}
ListNode.prototype[Symbol.iterator] = function () {
  return {
    p: this,
    next: function () {
      if (this.p !== null) {
        const val = this.p.val
        this.p = this.p.next
        return { value: val, done: false }
      }
      return { value: undefined, done: true }
    }
  }
}

生成器函數

暈了嗎,暈了就使用生成器函數吧!

生成器函數以 function* 標識,經過 yield 語句返回巧克力,函數執行完成就表示遍歷結束。

一樣以 LeetCode 中定義的鏈表類型爲例,迭代器是這樣實現的:

ListNode.prototype[Symbol.iterator] = function* () {
  let p = this
  while (p !== null) {
    yield p.val
    p = p.next
  }
}

另外生成器函數的返回值自己又是一個可迭代對象(擁有 Symbol.iterator 方法……且……且),因此生成器的返回值既能夠做爲某個對象的迭代器,也能夠直接拿來迭代。

效果展現

let xs = null

xs = [1, 2, 3]
for (const x of xs) { console.log(x) }

xs = new ListNode(1, new ListNode(2, new ListNode(3)))
for (const x of xs) { console.log(x) }

xs = new Set(); xs.add(1); xs.add(2); xs.add(3);
for (const x of xs) { console.log(x) }

僅當使用生成器函數實現迭代器時,也能夠這樣寫

for (const x of xs[Symbol.iterator]()) { console.log(x) }

登峯造極的 LINQ

結合一切爲了抽象的理念和迭代器設計模式的成功案例,一個偉大的願望在心中誕生:

若是不止遍歷,任何數據集的任何操做都如此直觀,咱們就能夠專心的考慮業務邏輯了!

LINQ 作到了!以迭代器的設計模式爲基礎,作到了!

咱們能夠經過一連串簡單的方法調用實現對數據集的一系列操做,以下直觀的代碼簡直像說人話同樣:

Enumerable.from(iterable).where(isNeeded).select(x => x.someProperty).distinct()
// iterable 是某個實現了迭代協議的可迭代對象
// Enumerable.form 將可迭代對象轉換爲了 LINQ 使用的枚舉對象
// 若是未來有一天 JavaScript 實現了官方的 LINQ 以上代碼有望寫成 iterable.where.select.distinct....

這個 LINQ 的實現位於 https://github.com/mihaifm/linq

相關文章
相關標籤/搜索