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

一切为了抽象

一段代码越抽象它的复用价值就越高,举一个极端例子: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

相关文章
相关标签/搜索