快速了解 ES6 的生成器

JavaScript 生成器.png

生成器是 ECMAScript 6 新增的一个极为灵活的结构,拥有一个函数块内暂停和恢复代码执行的能力。

生成器函数提供了一个强大的选择:它允许你定义一个包含自有迭代算法的函数,同时它可以自动维护自己的状态。

使用 function*语法定义生成器函数,而且 * 不受两侧空格的影响。只要是可以定义函数的地方,就可以定义生成器。

function* generator1() {}

<small>注意:箭头函数不能用来定义生成器函数。</small>

方法

调用生成器函数会产生一个符合可迭代协议和迭代器协议的生成器对象。生成器对象一开始处于暂停执行的状态。与迭代器相似,生成器也实现了 Iterator 接口。

Generator.prototype.next()

Generator 对象具有 next() 方法,调用这个方法会让生成器开始或恢复执行,而且返回的是一个 IteratorResult 对象,具有 done 属性和 value 属性。默认情况下,返回的值为 { done: true, value: undefined }

function* generator() {
    return 'sample';
}
const v1 = generator();
// 默认的迭代器是自引用的
console.log(v1);    // generator {<suspended>}
console.log(generator()[Symbol.iterator]());    // generator {<suspended>}
console.log(v1.next());    // { value: 'sample', done: true }

生成器还可以通过 next(value) 方法向内部传参,并且这个参数会变成 yield 的结果。

function* generator() {
    let v = yield 10;
    yield v;
}
let v = generator();
console.log(v.next(1));    // { value: 10, done: false }
console.log(v.next(2));    // { value: 2, done: false }

第一次调用 next() 传入的值不会被使用,因为这一次调用时为了开始执行生成器函数,它会执行并返回第一个 yield 10 的结果。第二次 next() 调用,获得了 2 作为结果let v = 2 并返回 yield v

Generator.prototype.return()

Generator 提供的 return() 方法返回给定的值并结束生成器。因此可以使用 return() 方法提前终止生成器。

function* generator() {
    yield 'sample';
    yield ;
    yield 'example';
    return 'instance';
}
const v1 = generator();
console.log(v1); // generator {<suspended>}
console.log(v1.return("quit")); // { done: true, value: "quit" }
console.log(v1); // generator {<closed>}

return() 方法没有提供参数,则返回对象的 value 属性的值为 undefined

当使用 return() 方法关闭了生成器后就无法恢复,后续调用 next() 方法返回的值为 {done: true, value: undefined}

console.log(v1.next()); // {done: true, value: undefined}

for-of 循环等内置语言结构会忽略状态为 done: trueIteratorObject 内部返回的值。

function* generator() {
    yield 'sample';
    yield ;
    yield 'example';
    return 'instance';
}
let v1 = generator1();
for (const x of v1) {
    if (x === 'example') {
        v1.return("quit");
    }
    console.log(x);
}
// sample
// undefined
// example

Generator.prototype.throw()

生成器还提供了 throw() 方法用来向生成器中注入一个错误。如果内部未处理该错误,那么生成器就会关闭。

function* generator() {
    yield 'sample';
    yield ;
    yield 'example';
    return 'instance';
}

const v = generator();
console.log(v);    // // generator {<suspended>}
try {
    v.throw(new Error("Throw Error"));
} catch (e) {
    console.log(e);    // Error: Throw Error
}
console.log(v);    // generator {<closed>}

但是生成器函数内部处理了这个错误,那么生成器就不会关闭,还可以恢复执行。错误处理会跳过对应的 yield,因此在这个例子中会跳过一个值。如下所示:

function* generator1() {
    for (const x of ["sample", "example", "instance"]) {
        try {
            yield x;
        } catch (e) {
            console.log("Error caught!");
        }
    }
}
const v = generator();
console.log(v.next());    // { value: "sample", done: false}
v.throw(new Error("Throw Error"));    // 
console.log(v);    // generator {<suspended>}
console.log(v.next);    // { value: "instance", done: false}

在这里,向生成器内部注入一个错误,该错误会被 yield 关键字抛出,并且在生成器内部的 try-catch 语句块中处理了该错误。此时,生成器函数还是会继续执行,但下次调用 next() 方法就不会产生 example 值,而是产生 instance 值。

<small>注意:如果生成器对象还没有开始执行,那么调用 throw() 抛出的错误不会在函数内部被捕获,因为这相当于在函数块外部抛出了错误。</small>

yield

ECMAScript 6 提供了 yield 关键字用来让生成器函数暂停,并且函数作用域的状态会保留。生成器对象会通过调用 next() 方法让生成器函数恢复执行。yield 省略表达式会返回 undefined

function* generator() {
    yield 'sample';
    yield ;
    yield 'example';
    return 'instance';
}
let v1 = generator();
console.log(v1.next());    // { value: 'sample', done: false }
console.log(v1.next());    // { value: undefined, done: false }
console.log(v1.next());    // { value: 'example', done: false }
console.log(v1.next());    // { value: 'instance', done: true }
console.log(v1.next());    // { value: undefined, done: true }

生成器对象可当成可迭代对象,可以使用 for...of 循环。

function* generator() {
    for (const x of ["sample", "example", "instance"]) {
        yield x;
    }
}
for (const x of generator1()) {
    console.log(x);
}
// sample
// example
// instance

<small>注意:yield 关键字只能在生成器函数内部使用且必须位于生成器函数定义中,出现在嵌套的非生成器函数中会抛出错误。</small>

还可以使用 yield* 语法增强 yield 的行为,用于委托给另一个 generator 或可迭代的对象。

function* generator() {
    for (const v of [1, 2, 3]) {
        yield v;
    }
}

等价于

function* generator() {
    yield* [1, 2, 3];
}

等价于

function* generator1() {
        yield 1;
        yield 2;
    }
function* generator2() {
    yield* generator1();
    yield 3;
}

<small>注意,yield* 两侧的空格不影响其行为。</small>

由于 yield* 可以调用另外一个生成器,所以通过 yield* 可以实现递归调用。

function* recs(n) {
    if (n > 1) {
        yield* f(n-1);
    }
    yield n;
}
for (const x of recs(3)) {
    console.log(x);
}
// 1
// 2
// 3

使用递归生成器结构和 yield* 可以优雅地表达递归算法。

小结

生成器是一种特殊的函数,调用之后会返回一个生成器对象。生成器对象实现了 Iterable 接口,因此应用场景与迭代器一样。生成器支持 yield 关键字,用于暂停执行生成器函数,与 next() 方法搭配产生一系列值。yield* 表达式可以在生成器中调用其它生成器。

更多内容请关注公众号「海人为记

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 225,132评论 6 523
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,457评论 3 404
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 172,411评论 0 368
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,147评论 1 301
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,145评论 6 400
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,611评论 1 315
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,962评论 3 429
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,948评论 0 279
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,479评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,514评论 3 347
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,640评论 1 355
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,228评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,973评论 3 340
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,402评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,551评论 1 277
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,210评论 3 381
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,714评论 2 366

推荐阅读更多精彩内容