JavaScript中的迭代器和生成器是什么?

6
在Mozilla的页面迭代器和生成器中,有这样一句话:

虽然自定义迭代器是一个有用的工具,但它们的创建需要小心编程,因为需要显式地维护其内部状态。生成器提供了一个强大的替代方案:它们允许您通过编写单个函数来定义迭代算法,该函数可以维护其自己的状态。

关于上述解释,难道不可能在没有生成器的情况下编写迭代算法吗,例如:
Array[Symbol.iterator] = function(){
    return {
        next: function(){
            //logic
            return {
                value: "",
                done:false
            }
        }
    }
}

我无法理解。有人能否解释一下为什么要创建一种替代品,对我来说似乎没有太大的区别。


next:是迭代函数,用于遍历数组中的每个变量。 - Evan Carslake
1
我建议你搜索“语法糖”(syntactic sugar) - zzzzBov
2个回答

13

它们表面上可能看起来很相似,但它们的使用方式可以非常不同。

迭代器和可迭代对象

迭代器有一个严格的定义:它们是包含一个next(可能还有其他一些)函数的对象(即迭代器)。每次调用next函数时,都期望返回一个具有两个属性的对象:

  • value:迭代器当前的值
  • done:迭代器是否完成?

另一方面,可迭代对象是具有带有Symbol.iterator键的属性的对象(该键表示众所周知的符号@@iterator)。该键包含一个函数,当调用该函数时,会返回一个新的迭代器。 以下是一个可迭代对象的示例:

const list = {
    entries: { 0: 'a', 1: 'b' },
    [Symbol.iterator]: function(){
        let counter = 0;
        const entries = this.entries;
        return {
            next: function(){
                return {
                    value: entries[counter],
                    done: !entries.hasOwnProperty(counter++)
                }
            }
        }
    }
};

正如它们的名称暗示的那样,它们的主要目的是提供可以进行迭代的接口:

for (let item of list) { console.log(item); }
// 'a'
// 'b'

生成器

相比之下,生成器更加灵活多样。可以把它们想象成可以暂停和恢复的函数。

虽然它们可以被迭代(它们的可迭代对象提供了一个next方法),但是它们可以实现更复杂的过程,并通过它们的next方法提供输入/输出通信。

一个简单的生成器:

function *mygen () {
   var myVal = yield 12;
   return myVal * 2;
}

const myIt = mygen();

const firstGenValue = myIt.next().value;
// Generator is paused and yields the first value

const result = myIt.next(firstGenValue * 2).value;

console.log(result); // 48

生成器委派

生成器可以委派给另一个生成器:

function *mydelgen(val) {
    yield val * 2;
}

function *mygen () {
    var myVal = yield 12;
    yield* mydelgen(myVal); // delegate to another generator
}

const myIt = mygen();
const val = myIt.next().value;
console.log(val);
console.log(myIt.next(val).value);
console.log(myIt.next().value);

生成器和承诺

通过使用类似于co的工具,可以将生成器和承诺结合在一起创建一种自动的异步迭代器。

co(function *(){
  // resolve multiple promises in parallel
  var a = Promise.resolve(1);
  var b = Promise.resolve(2);
  var c = Promise.resolve(3);
  var res = yield [a, b, c];
  console.log(res);
  // => [1, 2, 3]
}).catch(onerror);

总结

因此,可以说迭代器的主要目的是为自定义对象创建一个接口进行迭代,而生成器则提供了众多同步和异步工作流程的可能性:

  • 有状态函数
  • 生成器委托
  • 生成器和promise
  • CSP

等等。


"迭代器的定义相当严格。它们返回一个包含 next(和可能还有其他)函数的对象。" 这不是正确的。迭代器是对象,它们不返回任何东西。它们本身有一个 next 函数。或者你是指 可迭代对象,而不是迭代器吗? "迭代器的一个例子:" 在您的示例中,list 是一个可迭代对象。请参见 http://www.ecma-international.org/ecma-262/6.0/index.html#sec-iteration 。 - Felix Kling
@FelixKling 谢谢,这些术语有点让我困惑。在可迭代对象中分配给 Symbol.iterator 的函数有一个名称吗? - nils
@FelixKling 只是出于好奇,你是如何学会浏览规范的?我知道一些部分,但仍然有困难阅读其他部分(特别是语法部分,例如http://www.ecma-international.org/ecma-262/6.0/index.html#sec-object-initializer)。是否有指南在某个地方解释最重要的术语? - nils
这里有一些解释:http://www.ecma-international.org/ecma-262/6.0/index.html#sec-syntactic-grammar。阅读关于语法的一般性内容,特别是关于巴克斯-诺尔范式的内容会很有帮助。至于规范的具体细节,需要大量的阅读(根据需要),并逐渐深入更复杂的主题...即使我在理论上理解它们,仍然有一些符号我不能轻易地解释。当然,我现在理解规范所花费的时间很长。从小处着手,不试图一次理解所有内容对我很有帮助。 - Felix Kling

2
“不使用生成器编写迭代算法”是不可能的。是的,可以将每个生成器算法编写为自定义迭代器,但是您代码中的逻辑会更加复杂。这个陈述的重点在于它不再是迭代的,而是递归的。作为练习,这里有一个非常简单的迭代生成器函数:
function* traverseTree(node) {
    if (node == null) return;
    yield* traverseTree(node.left);
    yield node.value;
    yield* traverseTree(node.right);
}

尝试将其重写为自定义迭代器。无论你是否遇到困难或完成任务,都会展示出不同之处。

这是我曾经草草写下的一个可能的解决方案,但我甚至不确定它是否正确。 - Bergi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接