吃透 ES6 Generator:yield/next/yield* 核心用法详解

0 阅读3分钟

Generator 是 ES6 为异步编程和迭代器设计的核心特性,本文聚焦 Generator 函数的核心语法:yield 暂停执行、next () 传参机制、yield* 调用迭代器 / 生成器的逻辑,通过极简示例讲清原理;

ES6中也提出了新的循环方式for...of;它是只能用于可生成迭代对象的循环的;比如数组,Set, Map, String,自定义的迭代器等等;提这个知识点是它跟本文的知识点有关联;

一、Generator 定义

Generator主要用于异步编程,最大的特点就是交出函数的执行权(暂停);本质上,整个Generator函数就是一个封装的异步任务,或者说是异步任务的容器;

返回方式为yieldyield命令是异步不同阶段的分界线

yieldreturn的区别:

  • yield:暂停函数,再次调用会执行到遇到下一个yield为止;
  • return:结束函数,再次调用会重新执行,直到return结束;

Generator 函数体内使用yield语句,可以定义不同的内部状态;

函数的调用方法:next()方法;会返回一个对象,{ value: 值,done:  布尔值 }

function* print(){
    yield 'a';
    yield 'b';
    yield 'c';
    return 'd ...end';
}
let p = print();
console.log(p.next())
// { value: 'a', done: false}
console.log(p.next())
// { value: 'b', done: false}
console.log(p.next())
// { value: 'c', done: false}
console.log(p.next())
// { value: 'd ...end', done: true}
console.log(p.next())
// { value: undefined, done: true}

当输出完成后done会变成true;接着调用的还是能返回值,但是返回的都是undefined

二、next()

Generator函数中的.next()方法可以接收参数;

  • 传入的参数,其实是把上一个yield语句的返回的值给覆盖;
  • 第一个.next()方法其实就是启动器,在它之前没有yield语句,所以给第一个.next()方法传参是没有意义的
function* generatorFunction() {
  const a = yield;
  while(true) {
    yield a;
  }
}

const generator = generatorFunction();

console.log(generator.next());  // { value: undefined, done: false}
console.log(generator.next(1)); // { value: 1, done: false }
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 1, done: false }

第一次调用的时候yield没有返回语句,所以是undefined;第二次调用的时候传1,接收到的是1,往后只会一直输出1

三、yield 和 yield*

比如创建一个生成器用于返回一个斐波那契数列,斐波那契数列定义如下:

  • 第一个数是 0
  • 第二个数是 1
  • 之后的每一个数是前两个数之和

换言之,即:F(0) = 0; F(1) = 1; ... F(n) = F(n-1) + F(n-2);

实际上我们希望在一个生成器里输出一些来自其它生成器的值。这时 yield* 就派上用场了:

function* fibonacciGeneratorFunction(a = 0, b = 1) {
  yield a;   
  yield* fibonacciGeneratorFunction(b, b + a);
}

const fibonacciGenerator = fibonacciGeneratorFunction();

fibonacciGenerator.next(); // { value: 0, done: false }
fibonacciGenerator.next(); // { value: 1, done: false }
fibonacciGenerator.next(); // { value: 1, done: false }
fibonacciGenerator.next(); // { value: 2, done: false }
fibonacciGenerator.next(); // { value: 3, done: false }
fibonacciGenerator.next(); // { value: 5, done: false }

yield* 是可用于调用其他的生成器的;

function* g1() {
  yield 2;
  yield 3;
  yield 4;
}

function* g2() {
  yield 1;
  yield* g1();
  yield 5;
}

const iterator = g2();

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: 4, done: false}
console.log(iterator.next()); // {value: 5, done: false}
console.log(iterator.next()); // {value: undefined, done: true}

yield* 的用处

yield* 紧跟的表达式可以是任何可生成迭代对象的迭代器或另一个生成器;可生成迭代对象的ObjectArray, String, Set, Map等;可以通过原型链上的Symbol.Iterator判断是否可生成迭代对象;

Symbol.IteratorSymbol的那一篇(第一篇)有提

四、作用

数组的合并分级遍历

let a = [1,2,3,4]
let b = [5,6,7,8]
// let c = [...a,...b]
// for(let key of c) {
//     console.log(key);
// }
function* obPrint(...args) {
    console.log(...args);
    for(const key of args) {
        yield* key
    }
}
for(const num of obPrint(a,b)) {
    console.log(num);
}

感谢您抽出宝贵的时间观看本文;本文是JavaScript系列的第4篇,后续会持续更新,欢迎关注~