何为生成器?

生成器(generator)是能返回一个迭代器的函数。生成器函数由放在 function 关键字之后的一个星号(*)来表示,并能使用新的 yield 关键字。将星号紧跟在 function 关键字之后,或是在中间留出空格,都是没问题的,正如下例:

// generator
function *createIterator() {
    yield 1;
    yield 2;
    yield 3;
}

// generators are called like regular functions but return an iterator
let iterator = createIterator();

console.log(iterator.next().value);     // 1
console.log(iterator.next().value);     // 2
console.log(iterator.next().value);     // 3

createIterator() 前面的星号让此函数变成一个生成器。yield 关键字也是 ES6 新增的,指定了迭代器在被 next() 方法调用时应当按顺序返回的值。此例所生成的迭代器能够在 next() 方法调用成功时返回三个不同的值:先是 1,然后是 2,最后则是 3。生成器能像任意其他函数那样被调用,正如示例中创建 iterator 的代码。

生成器函数最有意思的方面可能就是它们会在每个 yield 语句后停止执行。例如,此代码中 yield 1 执行后,该函数将不会再执行任何操作,直到迭代器的 next() 方法被调用,此时才继续执行 yield 2。在函数中停止执行的能力是极其强大的,并能引出生成器函数的一些有趣的用法(详见 “迭代器高级功能” 一节) 。

yield 关键字可以和值或是表达式一起使用,因此你可以通过生成器给迭代器添加项目,而不是机械化地将项目一个个列出。作为一个例子,此处给出了在 for 循环内使用 yield 的方法:

function *createIterator(items) {
    for (let i = 0; i < items.length; i++) {
        yield items[i];
    }
}

let iterator = createIterator([1, 2, 3]);

console.log(iterator.next());           // "{ value: 1, done: false }"
console.log(iterator.next());           // "{ value: 2, done: false }"
console.log(iterator.next());           // "{ value: 3, done: false }"
console.log(iterator.next());           // "{ value: undefined, done: true }"

// for all further calls
console.log(iterator.next());           // "{ value: undefined, done: true }"

此例传递了一个名为 items 的数组给 createIterator() 生成器函数。在此函数内,for 循环在循环执行时从数组中返回元素给迭代器。每当遇到 yield,循环就会停止;而每当 iterator 上的 next() 方法被调用,循环就会再次执行到 yield 语句处。

生成器函数是 ES6 的一个重要特性,并且因为它就是函数,就能被用于所有可用函数的位置。本节剩余部分会集中于书写生成器的其他有用方法。

yield 关键字只能用在生成器内部,用于其他任意位置都是语法错误,即使在生成器内部的函数中也不行,正如此例:

function *createIterator(items) {
    items.forEach(function(item) {
        // 语法错误
        yield item + 1;
    });
}

尽管 yield 严格位于 createIterator() 内部,此代码仍然有语法错误,因为 yield 无法穿越函数边界。从这点上来说,yieldreturn 非常相似,在一个被嵌套的函数中无法将值返回给包含它的函数。

生成器函数表达式

你可以使用函数表达式来创建一个生成器,只要在 function 关键字与圆括号之间使用一个星号(*)即可。例如:

let createIterator = function *(items) {
    for (let i = 0; i < items.length; i++) {
        yield items[i];
    }
};

let iterator = createIterator([1, 2, 3]);

console.log(iterator.next());           // "{ value: 1, done: false }"
console.log(iterator.next());           // "{ value: 2, done: false }"
console.log(iterator.next());           // "{ value: 3, done: false }"
console.log(iterator.next());           // "{ value: undefined, done: true }"

// for all further calls
console.log(iterator.next());           // "{ value: undefined, done: true }"

此代码中的 createIterator() 是一个生成器函数表达式,而不是一个函数声明。星号放置在 function 关键字与圆括号之间,是因为这个函数表达式是匿名的。除此之外,此例与前一个版本的 createIterator() 函数没有区别,都使用了一个 for 循环。

不能将箭头函数创建为生成器。

生成器对象方法

由于生成器就是函数,因此也可以被添加到对象中。例如,你可以在 ES5 风格的对象字面量中使用函数表达式来创建一个生成器:

var o = {

    createIterator: function *(items) {
        for (let i = 0; i < items.length; i++) {
            yield items[i];
        }
    }
};

let iterator = o.createIterator([1, 2, 3]);

你也可以使用 ES6 方法的速记法,只要在方法名之前加上一个星号(*):

var o = {

    *createIterator(items) {
        for (let i = 0; i < items.length; i++) {
            yield items[i];
        }
    }
};

let iterator = o.createIterator([1, 2, 3]);

这些例子的功能等价于 “生成器函数表达式” 小节中的例子,只是语法有区别。在速记法版本中,由于 createIterator() 方法没有使用 function 关键字来定义,星号就紧贴在方法名之前,不过其实你可以在星号与方法名之间留下空格。