Skip to main content

Generator-生成器

什么是生成器?

  • 生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等。

    • 平时我们会编写很多的函数,这些函数终止的条件通常是返回值或者发生了异常。
  • 生成器函数也是一个函数,但是和普通的函数有一些区别:

    • 首先,生成器函数需要在function的后面加一个符号:*

    • 其次,生成器函数可以通过yield关键字来控制函数的执行流程:

    • 最后,生成器函数的返回值是一个Generator(生成器)

      • 生成器事实上是一种特殊的迭代器;

      • MDN:Instead, they return a special type of iterator, called a Generator.

生成器函数执行

  • 我们发现下面的生成器函数foo的执行体压根没有执行,它只是返回了一个生成器对象。

    • 那么我们如何可以让它执行函数中的东西呢?调用next即可;

      • next执行控制函数执行到yield的左边
    • 我们之前学习迭代器时,知道迭代器的next是会有返回值的;

    • 但是我们很多时候不希望next返回的是一个undefined,这个时候我们可以通过yield来返回结果;

/*
生成器函数:
1.function后面会跟上符号: *
2.代码的执行可以被yield控制
3.生成器函数默认在执行时, 返回一个生成器对象
* 要想执行函数内部的代码, 需要生成器对象, 调用它的next操作
* 当遇到yield时, 就会中断执行
*/

// 1.定义了一个生成器函数
function *foo(){
console.log('aaaa');
yield "1111"
console.log('bbbb');
yield "2222"
console.log('cccc');
}

// 2.调用生成器函数, 返回一个 生成器对象
const generator = foo();
// 调用next方法
console.log(generator.next()) ;//遇到第一个yield执行暂停,{value: '1111', done: false}
console.log(generator.next());//遇到第二个yield执行暂停,{value: '2222', done: false}
console.log(generator.next());//执行剩余代码,{value: undefined, done: true}

生成器传递参数 – next函数

  • 函数既然可以暂停来分段执行,那么函数应该是可以传递参数的
    • 我们在调用next函数的时候,可以给它传递参数,那么这个参数会作为上一个yield语句的返回值
    • 注意:也就是说我们是为本次的函数代码块执行提供了一个值;
function *foo(arg1){
console.log(arg1);
const arg2 = yield "1111"
console.log(arg2);//cccc
const arg3 = yield "2222"
console.log(arg3);//dddd
}

const generator = foo('aaaa');

console.log(generator.next()) ; //没有上一个yield,第一次执行,不可以传入参数,可以通过函数本身进行传参。
console.log(generator.next('cccc'));//arg2为cccc
console.log(generator.next('dddd'));//arg3为dddd

生成器提前结束 – return函数

  • 还有一个可以给生成器函数传递参数的方法是通过return函数:
    • return传值后这个生成器函数就会结束,之后调用next不会继续生成值了;
function *gen(arg1){
console.log(arg1);
const arg2 = yield "1111"
console.log(arg2);
const arg3 = yield "2222"
console.log(arg3);
const arg4 = yield "3333"
console.log(arg4);
}

const generator = gen('aaaa');

console.log(generator.next()) ;//{value: '1111', done: false}
console.log(generator.return('cccc'));//返回{value: 'cccc', done: true},生成器函数后面的代码都不会执行
console.log(generator.next('dddd'));//{value: undefined, done: true}
console.log(generator.next('eeee'));//{value: undefined, done: true}

生成器抛出异常 – throw函数

  • 除了给生成器函数内部传递参数之外,也可以给生成器函数内部抛出异常:
    • 抛出异常后我们可以在生成器函数中捕获异常;
    • 但是在catch语句中不能继续yield新的值了,但是可以在catch语句外使用yield继续中断函数的执行;

function *foo(){
console.log('函数开始执行');

try{
yield "1111"
}catch(err){
console.log('错误:',err);//错误: error
}
yield "2222"
console.log("函数执行结束");

}

let generator = foo();

console.log( generator.next());//{value: '1111', done: false}
console.log( generator.throw('error')) //{value: '2222', done: false} ,继续执行到下一个yield
console.log(generator.next());//{value: undefined, done: true}

生成器替代迭代器

  • 我们发现生成器是一种特殊的迭代器,那么在某些情况下我们可以使用生成器来替代迭代器:

    // 1.对之前的代码进行重构(用生成器函数)
    const names = ["abc", "cba", "nba"]

    //1.以前自己创建迭代器的做法
    // let index = 0
    // const namesIterator = {
    // next: function() {
    // // done: Boolean
    // // value: 具体值/undefined
    // if (index < names.length) {
    // return { done: false, value: names[index++] }
    // } else {
    // return { done: true }
    // }
    // }
    // }
    // console.log(namesIterator.next())

    //2.使用生成器替换迭代器
    function* createArrayIterator(arr) {
    for (let i = 0; i < arr.length; i++) {
    yield arr[i]
    }
    // yield arr[0]
    // yield arr[1]
    // yield arr[2]
    // return undefined
    }
    const namesIterator = createArrayIterator(names)
    console.log(namesIterator.next())

yield*语法糖

  • 事实上我们还可以使用yield*来遍历可迭代对象
    • yield* 后面必须是一个可迭代对象
    • 这个时候相当于是一种yield的语法糖,只不过会依次迭代这个可迭代对象,每次迭代其中的一个值;
const names = ["abc", "cba", "nba"]


function* createArrayIterator(arr) {
yield* arr
//相当于
// for(let item of arr){
// yield item
// }
}

const namesIterator = createArrayIterator(names)
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())

自定义类迭代 – 生成器实现

  • 在之前的自定义类迭代中,我们也可以换成生成器:
// 2.yield替换类中的实现
class Person {
constructor(name, age, height, friends) {
this.name = name
this.age = age
this.height = height
this.friends = friends
}

// 实例方法
*[Symbol.iterator]() {
yield* Object.entries(this)
}
}

const p = new Person("why", 18, 1.88, ["kobe", "james", "curry"])
for (const item of p) {
console.log(item)
}

//['name', 'why']
//['age', 18]
//['height', 1.88]
//['friends', Array(3)]