首页 > 其他 > 详细

Generator(生成器)函数

时间:2019-11-03 23:11:43      阅读:81      评论:0      收藏:0      [点我收藏+]

一.基础知识

Generator函数是ES6出现的一种异步操作实现方案。

异步即代码分两段,但是不是连续执行,第一段执行完后,去执行其他代码,等条件允许,再执行第二段。

同步即代码连续执行。

1. Generator函数是什么?

Generator函数是一种遍历器生成函数;运行后返回一个遍历器对象。

函数内部可以看作一个状态机;可以通过遍历器的next方法遍历函数内部的各个状态。

Generator函数不是构造函数!不能使用new命令生成实例。否则报错!

但是生成的实例是Generator函数的实例对象。

function* testGen() {
  yield 1;
}
const gen = testGen();
console.log(gen instanceof testGen); //生成的对象是Generator的实例
console.log(gen.__proto__ === testGen.prototype); // 生成的实例继承原型上的属性和方法

// 运行结果 
true
true

构造函数本质是通过new命令,返回this对象,即实例对象。

但是Generator函数不返回this,它返回遍历器。通过直接执行返回。

此时函数内部的this是函数执行时所在的对象,即window(非严格模式,严格模式下是undefined)

<script type="module">
  function* testGen() {
    console.log(this);
  }
  const gen = testGen();
  gen.next();
</script>
  // 运行结果如下(严格模式)
  undefined

如果想要生成的遍历器对象可以访问this的属性和方法,可以使用call或者apply方法绑定this到其原型对象上。

  function* testGen() {
    this.a = 5;
    return this;
  }
  const gen = testGen.call(testGen.prototype);
  const thisObj = gen.next().value;// gen.next().value === testGen.prototype
  console.log(thisObj.a); // 5

2. Generator函数声明

1.作为函数声明

function* fnName() { // function和函数名称之间有一个*, 通常紧跟function后
  yield 表达式; // 函数内部使用yield命令
  return val; //表示最后一个状态
}
const myGenerator = fnName(); //生成遍历器对象;相当于指向内部状态的指针对象

其中,*是Generator函数的标识;yield(“产生”)是状态生成命令。

2. Generator函数作为对象的属性声明

const obj = {
  *testGen() {
    ...
  }
}
// 相当于 
const obj = {
  testGen: function* () {
    ...
  }
}

3. Generator函数生成遍历器对象

调用Generator函数后,会返回一个遍历器对象;但是此时函数内部代码并不执行。

遍历器对象可以通过自身的next方法访问Generator函数内部的状态。

yield命令除了表示生成状态,还表示暂停标识,即next方法遇到yield命令即停止运行。

所以next()方法表示从代码起始或者上一次停止的位置运行到下一个yield或者return或者代码结束。

next方法返回结果格式如下:

{value: 值, done: true/false} 
// value是当次运行的状态,即yield命令后面的表达式的值
// done表示是否遍历结束

想继续执行就要继续调用next方法,如果函数内部没有return,则一直运行到代码结束;

返回结果最后是:

{value: undefined, done: true}
//继续调用next方法,会一直返回该值

如果遇到return value,则return语句即表示遍历结束。

返回结果是:

{value: value, done: true}
//继续调用next方法,返回
{value: undefined, done: true}
// 继续调用next,一直返回该值

示例: 

function* testGen() {
  yield 1;
  return 2;
}
const gen = testGen();
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
// 运行结果如下
{value: 1, done: false}
{value: 2, done: true}
{value: undefined, done: true}
{value: undefined, done: true}

?可以遍历遍历器状态的遍历方法

技术分享图片
function* testGen() {
  yield 1;
  yield 2;
  yield 3;
}
const gen = testGen();
/****1. 通过扩展运算符...*******/
let result1 = [...gen]; 
console.log(result1)
/****2. 通过for...of遍历*******/
let result2 = [];
for(let item of gen) {
  result2.push(item);
}
console.log(result2)
/****3. 通过数组解构赋值********/
let [...result3] = gen;
console.log(result3)

/****4. 通过Array.from********/
let result4 = Array.from(gen);
console.log(result4)

// 运行结果如下:
[1,2,3] // 遍历器已经遍历结束,继续遍历后面会全部是[]
[]
[]
[]
一个遍历器只能遍历一遍
技术分享图片
function* testGen() {
  yield 1;
  yield 2;
  yield 3;
}
const gen1 = testGen();
/****1. 通过扩展运算符...*******/
let result1 = [...gen1]; 
console.log(result1)
/****2. 通过for...of遍历*******/
const gen2 = testGen();
let result2 = [];
for(let item of gen2) {
  result2.push(item);
}
console.log(result2)
/****3. 通过数组解构赋值********/
const gen3 = testGen();
let [...result3] = gen3;
console.log(result3)

/****4. 通过Array.from********/
const gen4 = testGen();
let result4 = Array.from(gen4);
console.log(result4)

// 运行结果如下:
[1,2,3]
[1,2,3]
[1,2,3]
[1,2,3]
四种遍历遍历器的方法

4. yield表达式

1. Generator函数中yield表达式可以不存在

此时Generator函数是一个等待next方法启动的暂缓执行函数。

function* testGen() {
  console.log(‘等待执行‘);
}
const gen = testGen();
setTimeout(() => {
  gen.next(); // 2秒后打印‘等待执行‘
}, 2000)

2. yield表达式最近的函数只能是Generator函数,否则声明时就会报错

function* testGen() {
  [1,2].forEach(function(item) { //回调函数不是Generator函数;可以改为for循环
    yield item; //Uncaught SyntaxError: Unexpected identifier
  })
}

3. yield表达式不是单独使用或者不是在=右侧时,需要将其括起来

function* testGen() {
  console.log(1 + (yield 1));
}

4. yield表达式本身不返回任何值,或者说返回undefined

function* testGen() {
  let r1 = yield 1;
  let r2 = yield 2;
  console.log(r1, r2);
}
let gen = testGen();
gen.next();
gen.next();
gen.next(); //遍历结束
// 运行结果如下
undefined undefined

5. 带参数的next方法

gen.next(value);

带参数的next方法,其中的参数表示上一次yield表达式的返回值。

所以第一次next方法如果有参数,参数无效,因为第一个next从代码起始执行,之前没有yield表达式。

function* testGen() {
  let r1 = yield 1;
  let r2 = yield 2;
  console.log(r1, r2);
}
let gen = testGen();
gen.next(‘是啥都没有用‘);
gen.next(‘give1‘); //第一个yield返回give1
gen.next(‘give2‘); //第二个yield返回give2
// 运行结果如下
give1 give2

如果想要第一个next方法的参数起作用,可以将Generator函数包装一下

function* testGen() {
  let r1 = yield 1;
  let r2 = yield 2;
  console.log(r1, r2);
}
function wrapper(fn) {
  let gen = fn();
  gen.next();
  return gen;
}
let gen = wrapper(testGen);
gen.next(‘give1‘); //第一个yield返回give1
gen.next(‘give2‘); //第二个yield返回give2
// 运行结果如下
give1 give2

上面的示例也说明,Generator函数的执行进度可以保留。

二.Generator实例方法

1. Generator.prototype.throw()

从遍历器对象上手动抛出异常。

该异常可以被Generator函数内部的try...catch捕获;抛出异常的位置位于遍历器当前状态指针所在的位置。

技术分享图片
function* testGen() {
  try{
    yield;
  } catch(err) {
    console.log(‘innerErr-->‘,err);
  }
  yield 1;
}
const gen = testGen();
gen.next(); 
gen.throw(new Error("gen.throw")); // 在第二个yield位置抛出异常,无法捕获
console.log(‘after‘);
// 运行结果
innerErr-->Error: gen.throw
after
innerError1
技术分享图片
function* testGen() {
  try{
    yield;
  } catch(err) {
    console.log(‘innerErr-->‘,err);
  }
  yield 1;
}
const gen = testGen();
gen.next(); 
gen.next(); // 比上面的示例多了一个next方法
gen.throw(new Error("gen.throw")); // 在第二个yield位置抛出异常,无法捕获
console.log(‘after‘);
// 运行结果
Uncaught Error: gen.throw //后面的不再执行
示例2:throw抛出异常位置

也可以被外部调用该方法的位置的catch捕获。

技术分享图片
function* testGen() {
  yield;
  yield 1;
}
const gen = testGen();
gen.next(); 
try {
  gen.throw(new Error("gen.throw")); // 在第二个yield位置抛出异常,无法捕获
} catch (error) {
  console.log(‘outer-->‘,error);
}
console.log(‘after‘);
// 运行结果
outer-->Error: gen.throw
after
外部捕获

当内部和外部try...catch同时存在时,错误先被Generator函数内部捕获;

技术分享图片
function* testGen() {
  try {
    yield;
  } catch(err) {
    console.log(‘inner-->‘,err);
  }
  yield 1;
}
const gen = testGen();
gen.next(); 
try {
  gen.throw(new Error("gen.throw")); // 在第二个yield位置抛出异常,无法捕获
} catch (error) {
  console.log(‘outer-->‘,error);
}
console.log(‘after‘);
// 运行结果
inner-->Error: gen.throw
after
内外部同时存在

注意:try...catch相当于一次性错误捕获器,捕获一次错误,后面再有错误需要另外一个捕获器。

 

四.应用

1. 给对象添加iterator接口-[Symbol.iterator]

诸如数组等可遍历对象本质是本身含有[Symbol.iterator]属性,该属性是一个遍历器生成方法。

使用for...of或者next()或者...进行遍历时,本质上遍历的是[Symbol.iterator]方法。

而Generator方法本身就是一个遍历器生成方法。

所以可以通过将Generator函数赋值给目标对象,使其具有iterator接口。

示例:给不具有iterator接口的普通对象添加iterator接口

技术分享图片
// 普通对象没有iterator接口,所以不能使用for..of
function* testGen() {
  let r1 = yield 1;
  let r2 = yield 2;
  console.log(r1, r2);
  return 3;
}
let obj = {
  a: 1
}
obj[Symbol.iterator] = testGen;
for (let item of obj) { // obj添加[Symbol.iterator]之前不能被遍历
  console.log(item); //item是内部的状态值
}
console.log([...obj]); 
// 运行结果如下
1
2
undefined undefined //for...of
undefined undefined // ...遍历
[1,2]
View Code

从上面可知:

for...of不需要手动遍历,会自动遍历生成器内部的所有状态,但是不会遍历done为true的值。

所以上面3不会被遍历。

基础语法:break对于for,while来说都是终止之后的循环;continue终止本次循环

示例: 实现fibonacci数列

技术分享图片
function* fibonacci() {
  let [prev, curr] = [0, 1];
  yield curr;
  while(true) {
    [prev, curr] = [curr, prev+curr]; 
    yield curr;   
  }
}
for(let item of fibonacci()) {
  if (item === 8) {
    break;
  }
  console.log(item);
}
// 运行结果如下:
1
1
2
3
5
Generator实现fibonacci数列

示例:模拟Object.entries()方法

技术分享图片
// Object.entries(obj)可以使用for...of遍历
// 说明该方法返回遍历器;写一个方法覆盖Object上的原有方法
Object.entries = function* (obj) {
  let objKeys = Object.keys(obj);
  for (let key of objKeys) {
    console.log(‘---selfdefined---‘);
    yield [key, obj[key]];
  }
}
let obj = {a:1,b:2};
for(let [key, value] of Object.entries(obj)) {
  console.log([key,value])
}
// 运行结果如下:
---selfdefined---
[‘a‘,1]
---selfdefined---
[‘b‘,2]
Object.entries源码模拟

 

Generator(生成器)函数

原文:https://www.cnblogs.com/lyraLee/p/11789402.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!