从零开始学_JavaScript_系列(53)——Generator函数(1)基本概念和示例

1、Generator基本概念

注:【遍历器】和【迭代器】是一个意思。

1.1、表象:
  1. 函数名有个星号(就是乘号*);
  2. 函数体内部使用yield表达式(yield翻译成中文:产生);
  3. 函数调用后返回值是一个遍历器对象,该对象可以调用next方法遍历yield表达式返回的值;
  4. 函数内部可有返回值,也可以没有(这个影响的是遍历器遍历到第一次done为true时,value的值是什么);
  5. 函数执行后并不直接执行函数内部代码,而是返回一个遍历器(参考上一章Iterator),但注意,他本身并没有[Symbol.iterator]属性

简单来理解,就是当函数名前面有星号时,调用函数的时候不会运行该函数,而是返回一个遍历器,该遍历器通过调用next方法可以遍历执行函数内部的语句,每次执行到yield表达式(或执行到函数结束)为止;

如示例代码:

function* foo() {   // 星号在function关键词和函数名之间
    yield 'first';  // yield表达式表示会被遍历到的内容
    let second = 'sec'; // 函数内部可以有正常的js语句
    yield second + 'ond';   // yield表达式只关心该表达式的返回值(即"second"字符串)
    return 'last'   // 可以有return返回值也可以没有
}

foo[Symbol.iterator];    // undefined,说明没有这个属性

let bar = foo();
// bar:
// {[[GeneratorStatus]]: "suspended"}
// 原型链上有nextreturn、throw三个方法

bar.next(); // {value: "first", done: false}
bar.next(); // {value: "second", done: false}
bar.next(); // {value: "last", done: true}  如果没有return,那么这里的value是undefined
bar;    // {[[GeneratorStatus]]: "closed"}  出现done之后才会closed
1.2、运行:

函数本身的运行:

  1. Generator函数可以视为一个状态机;
  2. 简单来说,就是内部可能有A、B、C状态,初始是空状态,然后通过多次调用next方法,依次切换到A、B、C状态;
  3. 当之后没有其他状态时,调用next方法返回的对象的done属性的值为true;


这三句话可能比较抽象,可以结合下面代码的执行来理解:

代码的执行:

  1. 直接调用本函数,并不会执行函数内部的代码,而是返回一个遍历器;
  2. 此时什么都不执行,直到调用该遍历器的next方法为止;
  3. 第一次调用next时,从函数内部第一行开始执行,直到遇见yield表达式为止,输出yield表达式的值(即yield所在的那一段js语句的返回值);
  4. 第二次调用next时,从上一次yield表达式停止的地方开始继续执行,直到再次遇见yield表达式,或者到函数的结尾,或遇见return后停止;
  5. 注意,是执行到yield表达式为止,而不是yield所在的那一行代码,典型情况是:console.log(yield "test");其中的console.log是不执行的;
  6. 每次执行next()后,返回值的是一个对象,有value属性和done属性,参考Iterator,其中value的值是yield表达式的值;
  7. 当结束到结尾时,或者遇见return时,done会变为true,在此之前,done为false;
  8. done变为true,没必要继续调用next了(就像Iterator接口那样);


首先,当然是上示例代码啦:

function* foo() {
    let a = 1;
    console.log('第一次执行')
    yield a;
    a++;
    console.log(yield "test");
    a *= 2;
    return a
}

// 调用foo()
let bar = foo();
// 此时没有输出任何东西,bar的值

// 第一次调用next(),下面的两行注释,其中第一行是console.log,第二行是返回值****
bar.next();
// 第一次执行
// {value: 1, done: false}

// 第二次调用next(),注意,这个时候没执行yield "test"外面的console.log
bar.next();
// {value: "test", done: false}

// 第三次调用next(),这个时候执行了console.log(),但显然由于yield之前已经执行了,所以参数是空
// 遇见return了,所以done变为true,value的值是return的返回值
bar.next();
// undefined
// {value: 4, done: true}

看注释,就懂代码怎么执行的了。

另外提一句,每次通过foo()生成的遍历器,都是独立的,他们之间不会互相影响。

1.3、yield关键字

首先,yield在非Generator函数内不是关键字,只有在Generator函数内才是关键字,可以通过赋值,然后不会报错来证明。

因此,不要试图在非Generator函数内部的场合使用yield,肯定会报错的啦。

let yield = 1;
console.log(yield);   //1
1.4、yield表达式的值

yield表达式起到的是暂停函数执行的作用,该表达式的值是下一次调用next()时作为参数传入的值,默认情况下是undefined

原因在于,每次执行到yield表达式时就会终止,等到下次执行的时候,相当于yield表达式所在的地方为空(上次执行过了,所以这次不会再执行一遍),所以值undefined。

那么怎么让yield表达式有值呢?答案是通过遍历器的next()方法的参数来传入。该参数将作为上一个yield表达式的值来使用;

function bar(val) {
    console.log(val, arguments.length)
}

function* foo() {
    console.log('Hello ' + (yield 123));
    bar(yield 456)
}

let test = foo();

test.next();
// {value: 123, done: false}

test.next();
// Hello undefined
// {value: 456, done: false}

test.next('input');
// input 1
// {value: undefined, done: true}

另外提一句,由于第一次调用next()时,之前是没有yield表达式的,所以如果想在初始化的时候就输入值,需要进行特殊化处理,比如外面包一层,或者放弃第一次next输入参数,空调用一次next()来实现(这个也是外面包一层的实现原理)

如示例代码(引自阮一峰的):

// 包一层
function wrapper(generatorFunction) {
  return function (...args) {
    let generatorObject = generatorFunction(...args);
    generatorObject.next();
    return generatorObject;
  };
}

const wrapped = wrapper(function* () {
  console.log(`First input: ${yield}`);
  return 'DONE';
});

wrapped().next('hello!')
// First input: hello!
// {value: "DONE", done: true}
// 空调用一次next()
function* dataConsumer() {
    console.log(`1. ${yield}`);
    console.log(`2. ${yield}`);
    return 'result';
}

let genObj = dataConsumer();
genObj.next();
genObj.next('a')
// 1. a
genObj.next('b')
// 2. b

最后,yield表达式如果单独使用,那么不需要括号,如果要将yield表达式与其他变量进行运算,那么需要使用圆括号将其括起来(如上面的示例);

1.5、Generator的简写

普通函数作为对象属性的时候可以简写,Generator函数作为对象属性的时候,虽然多了一个星号,但也可以简写,简写方法是将星号放在属性名前即可

如代码:

let foo = {
    * [Symbol.iterator]() {
        // some code

    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值