您现在的位置是:首页 > 技术学习 > javascript 网站首页 技术学习 javascript

JS生成器 - generator

简介 generator(生成器)是ES6标准引入的新的数据类型。最大特点就是可以交出函数的执行权(即暂停执行)。一个generator看上去像一个函数,区别就是函数名前面多了一个星号 *,但可以返回多次。

generator(生成器)是ES6标准引入的新的数据类型。最大特点就是可以交出函数的执行权(即暂停执行)。一个generator看上去像一个函数,区别就是函数名前面多了一个星号 *,但可以返回多次。与yield命令配合,可以实现暂停执行的功能。

1. 基本使用方法

function* testGenerator(a) {
  console.log('a');
  yield ++a; 

  console.log('b');
  yield ++a;

  console.log('c');
  return ++a;    
  console.log('d');
}

var gener = testGenerator(5);    
// 函数里的log并没有输出,所以函数并没有执行,

var gener2 = testGenerator(1);    
// gener 和 gener2 之间互不印象,

console.log(gener);    
// testGenerator{<suspended>}   (generator对象), 说明返回的是一个对象

var yield1 = gener.next();    
// a  (输出了一个a,第一个yield前的log执行了)

console.log(yield1);  
// {value: 6, done: false}     next函数返回的是一个 包含值和状态的 对象

console.log(yield1.value);    
// 6    (value是yield的返回的值)

console.log(yield1.done);    
// false    (done是yield的返回状态,false代表目前处于暂停状态,还没执行完)


var yield2 = gener.next();  
// b  (输出一个b,可见函数是接着第一个yield后面的代码继续执行的,到第二个yield停止)

console.log(yield2);  
// {value: 7, done: false}

console.log(yield2.value);  
// 7    (第二次返回了7,说明此时的变量值是接着 第一个yield的 变量值(此时a=6)使用的)

console.log(yield2.done); 
// false    ()

var yield3 = gener.next();  
// c  (输出一个c,可见函数是接着第二个yield后面的代码继续执行的,到return停止)

console.log(yield3);  
// {value: 8, done: true}

console.log(yield3.value);  
// 8

console.log(yield3.done);  
// true

var yield4 = gener.next();  
// (没有输出,说明执行的renturn之后不会再往下执行)

console.log(yield4);  
// {value: undefined, done: true}  (执行完return,以后再 next() 返回的都是 undefind)

2. next方法

  1. 遇到yield语句,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性的值。
  2. 再次调用next方法时,再继续往下执行,直到遇到下一个yield语句。
  3. 如果没有再遇到新的yield语句,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
  4. 如果该函数没有return语句,则返回的对象的value属性值为undefined。
  5. next方法可以有参数, next方法参数的作用,是覆盖掉上一个yield语句的值。
function* testNext() {
  var a = 1;
  var b = yield ++a;
  console.log(b);

  var c = yield ++a;
  console.log(c);

  return ++a;
}

var gener3 = testNext();

// var v1 = gener3.next(3);  
// 第一次执行next的时候传值,由于此时并没有 上一个 yield 的值,所以此时传值不起作用

var v1 = gener3.next();  
// 没有输出 语句:var b = yield ++a;  
// 先执行等号右边的运算,此时执行到 yield ++a, 返回++a(2) 逻辑暂停,即此时 b并没有附上值。

console.log(v1);  
// {value: 2, done: false}

var v2 = gener3.next(6);  
// 6    
// 执行第二个next,此时,有传参,先将上一个 yield 的值(++a  2) 替换为 参数6,
// 然后接着执行语句,给b赋值, 所以此时 b = 6

console.log(v2);  
// {value: 3, done: false}   
// 第一个yield执行完,a的值为2, 所以第二个 yield 返回 ++a  时候,返回值为3

var v3 = gener3.next(10);  
// 10

console.log(v3);  
// {value: 4, done: true},   同上

3. throw方法

  1. 在函数体外抛出错误,然后在Generator函数体内捕获。
function* testThrow() {
  console.log(1)
  yield 1;

  try {
    console.log(2)
    yield 2;
    console.log(3)
    yield 3;
  } catch (e) {
    console.log('第一个catch:' + e);
  }
  console.log(4);
  yield 4;

  try{
    console.log(5);
    yield 5;
  } catch (e) {
    console.log('第二个catch:' + e);
  }

  console.log(6);
  return 6;
}

var gener4 = testThrow();
var t1 = gener4.next();  
// 1

console.log(t1);  
// {value: 1, done: false}

// gener4.throw('试着抛出个异常 ^_^!');    
// 报错:Uncaught 试着抛出个异常 ^_^!;     
// 由于第一次next并没有try...catch... 这时手动抛出异常,异常无法被捕获,代码不再往下执行了。
// 之后再调用 next 将返回 {value: undefined, done: true}

var t2 = gener4.next();  
// 2

console.log(t2);  
// {value: 2, done: false}

gener4.throw('这个异常1可以被catch到 ^_^');  
// 第一个catch:这个异常1可以被catch到 ^_^    
// 要抛出的异常,被generator里的catch给捕获到了

// 4     
// 当捕获到异常后,代码会自动再执行一次 next() 方法,
// 此时控制台输出了四,说明try 里面的第二个 yield 并没有被执行,
// 可见,一个try代码块里面只能有一个yield生效

// gener4.throw('这个异常2不可以被catch到 ^_^');        
// Uncaught 这个异常2不可以被catch到 ^_^    
// 再次手动抛出异常就不能被捕获了,此时代码已经执行到 yield 4; 了

var t3 = gener4.next();  
// 5

console.log(t3);  
// {value: 5, done: false}

gener4.throw('这个异常3可以被catch到 ^_^');  
// 第二个catch:这个异常3可以被catch到 ^_^

// 6  
// catch捕获后,执行了下一个next(); 此时执行完,已经执行完 return 6; 了

var t4 = gener4.next();
console.log(t4);  
// {value: undefined, done: true}   
// 上一个catch之后,已经把return执行了,所以再次执行 next 返回 {value: undefined, done: true}

4. return 方法

  1. 可以返回给定的值(覆盖本次yield的值),并且终结遍历Generator函数。
function* testReturn() {
  yield 'a';
  yield 'b';
  yield 'c';
}

var gener5 = testReturn();

var g1 = gener5.next();
console.log(g1);  
// {value: "a", done: false}

var g2 = gener5.return('e');
console.log(g2);  
// {value: "e", done: true}  
// 强行第二次yield的值b为 指定值的 e,并终止generator函数执行

var g3 = gener5.next();
console.log(g3);  
// {value: undefined, done: true}    
// 上面已终止执行

5. for…of…

  1. for…of…循环可以自动遍历Generator函数生成的Iterator对象,且此时不再需要调用next方法
function* testForOf() {
  yield 'J';
  yield 'Q';
  yield 'K';
  return 'A';
}

var gener6 = testForOf();

for(var f of gener6) {
  console.log(f);
  // J     
  // for...of循环可以自动遍历Generator函数时生成的Iterator对象,且此时不再需要调用next方法。

  // Q     

  // K  
  // 一旦next方法的返回对象的done属性为true,for...of循环就会中止,且不包含该返回对象, 所以没输出A
}

var f1 = gener6.next();
console.log(f1);  
// {value: undefined, done: true}    

// for...of...相当于调用了next,当for循环结束,generator已经执行到 done:true 状态了

6. yield 语句

  1. yield语句只能用于function的作用域,如果function的内部还定义了其他的普通函数,则函数内部不允许使用yield语句。
  2. yield* 在Generater函数内部,调用另一个Generator函数
function* testYield() {
  yield 'x';

  // function inFn() {
  // yield 'y';   
      // Uncaught SyntaxError: Unexpected string    (报错, 普通函数里无法使用yield)
  // }

  yield 'y';
}

function* testYield2() {
  yield 'xx';
  testYield();
  yield 'yy';
}

for(var m of testYield2()) {
  console.log(m);
  // xx
  // yy
  // (在testYield2中无法访问到testYield中的值)
}

function* testYield3() {
  yield 'xxx';
  yield* testYield();
  yield 'yyy';
}

for(var n of testYield3()) {
  console.log(n);
  // xxx
  // x
  // y
  // yyy
  // (yield* 在testYield3中可以访问到testYield中的值)
}

个人CSDN博客

Top