Generator生成器对象
MDN:生成器对象是由一个 generator function 返回的,并且它符合可迭代协议和迭代器协议。generator(生成器)是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次。
【语法】
注意yield
关键字必须放在带*
指针的函数里面用
next()
方法会执行generator
的代码,然后,每次遇到yield x;
就返回一个对象{value: x, done: true/false}
,然后“暂停”。返回的value
就是yield
的返回值,done
表示这个generator是否已经执行结束了。如果done
为true
,则value
就是return
的返回值。(遇到yield
就移动指针并标记done:false
。遇到return不再迭代就立即标记done:ture
并返回,所以return不属于迭代范围内,因为done:true
)
当执行到done
为true
时,这个generator对象就已经全部执行完毕,不要再继续调用next()
了。
//例子:
//一遇到yeild然后执行yeild后面的值,执行完毕就暂停
function* read(){
let a = yield 'hello';
console.log(a);
let b = yield 'world';
console.log(b);
}
let it = read()
console.log(it.next())//{ value: 'hello', done: false } undefined
console.log(it.next())//{ value: 'world', done: false } undefined
console.log(it.next())//{ value: undefined, done: true }
第二个方法是直接用for ... of
循环迭代generator对象,这种方式不需要我们自己判断done
:(只会执行done:false
的情况并返回,return不会执行)
function* gen() {
yield 1;
yield 2;
yield 3;
return 4
}
let g = gen();
console.log(g);// "Object [Generator] {}"
//需要使用.next()方法或者for(..of..)来迭代执行
console.log(g.next());//{ value: 1, done: false }
console.log(g.next());//{ value: 2, done: false }
console.log(g.next());//{ value: 3, done: false }
console.log(g.next());//{ value: 4, done: true }
console.log(g.next());//{ value: undefined, done: true }
使用for...of...
迭代
function* gen() {
yield 1;
yield 2;
yield 3;
return 4
}
let g = gen();
//第二个方法是直接用for ... of循环迭代generator对象,
//这种方式不需要我们自己判断done:
for(let v of g){
console.log(v)//一次打印1,2,3
}
//你会发现并不会打印return后的4,因为这样迭代只会执行done:false的情况
我发现在**Generator
生成器函数只能被迭代一次**,因为上面两种方法同时使用的话只会执行最先使用的那种迭代方式:
function* gen() {
yield 1;
yield 2;
yield 3;
return 4
}
let g = gen();
for(let v of g){
console.log(v)
}
console.log(g.next());
console.log(g.next());
console.log(g.next());
console.log(g.next());
console.log(g.next());
//1
//2
//3
//{ value: undefined, done: true }
//{ value: undefined, done: true }
//{ value: undefined, done: true }
//{ value: undefined, done: true }
//{ value: undefined, done: true }
//使用for..of..迭代之后,再去使用.next()也被认为是迭代完成,不能重新迭代出结果
function* gen() {
yield 1;
yield 2;
yield 3;
return 4
}
let g = gen();
console.log(g.next());
console.log(g.next());
console.log(g.next());console.log(g.next());
console.log(g.next());
for(let v of g){
console.log(v)
}
//{ value: 1, done: false }
//{ value: 2, done: false }
//{ value: 3, done: false }
//{ value: 4, done: true }
//{ value: undefined, done: true }
//并不会再去迭代
【总结】
- 这个
*
就是一个指针拿来做标记,首先是指向的函数本身并标记此函数是生成器函数然后可以使用yield
关键字,可以有.next()
或者for..of
进行迭代操作。 - 对于
.next()
或者for..of
两种方式的迭代操作而言,其实原理都是一样的。迭代执行就是移动*
指针的过程,执行一步,就将指针移到yield
关键字上并且标记上done:false
(意思是迭代器还没有工作结束完成),①当以后不再有yield
关键字就为下次跌代标记上done:true
②当遇到return
关键字立即标记上done:true
跳出跌代。
【方法】
-
返回一个由
yield
表达式生成的值。如果生成器函数中有return
那么.next()
遇到return
立即返回当前计算值{value:xxx,done:true}//表示已经迭代完成
,之后再调用.next()
将返回{value:undefined,done:true}
, -
返回给定的值并结束生成器。
-
向生成器抛出一个错误。
【示例】
一个无限迭代器
function* idMaker(){
let index = 0;
while(true)
yield index++;
}
let gen = idMaker(); // "Generator { }"
console.log(gen.next().value);
// 0
console.log(gen.next().value);
// 1
console.log(gen.next().value);
// 2
// ...
【进阶】
使用生成器对象实现promise,解决回调地狱的问题,简化代码。
使用同步代码的方式写异步代码(但实际上还是执行的异步操作)
【异步操作】
使用场景:dva.js
Generator ES6规范里 可以和promise进行配合使用。
特点:可以暂停函数执行
yield
产出
* generator
生成器,生产的是什么呢?=>iterator
迭代器
function* generator(){
yield 1
yield 2
yield 3
return 100
}
let it = generator()
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
//结果:
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: 100, done: true }
{ value: undefined, done: true }
在ES6里面有一个 async await 就是 generator yield 的和语法糖。我们就进一步了解生成器Generator和Promise的关系。
async await =转换=> generator yield +co(大神tj写的一个nodejs库)
//在./xxx.txt文本文件里面存放着一个'./aaa.txt'文本
//在./aaa.txt文本文件里面存放着一个'哈哈哈'的文本
let fs = require('fs').promises;//他可以直接将fs中的方法变成Promise 10+
function* read() {
let content = yield fs.readFile('./xxx.txt','utf8');
let r = yield fs.readFile(content,'utf8');
return r;
}
let it = read()
let {value,done} = it.next()
Promise.resolve(value).then(data=>{
let {value,done} = it.next(data)//作为上一个yield的返回值
Promise.resolve(value).then(data=>{
let {value,done} = it.next(data)//作为上一个yield的返回值
console.log(value)//哈哈哈
})
})
上面的代码虽然是通过使用generator和yield实现了我们想要的功能,但是需要重复的调用差不多的代码。一般情况下我们为了简化重复的代码,都可以使用循环或者递归的方式,但是这里是异步的情况,循环不支持异步。所以就选择递归来实现。(也就是tj大神写的co库)
//优化代码,co的思路
let fs = require('fs').promises;//他可以直接将fs中的方法变成Promise 10+
function* read() {
let content = yield fs.readFile('./xxx.txt','utf8');
let r = yield fs.readFile(content,'utf8');
return r;
}
function co(it) {
return new Promise((resolve,reject)=>{
function next(data){
let {value,done} = it.next()
if(!done){//如果done一直是flase
Promise.resolve(value).then(res=>{//yield的后面可能是普通值,需要强转
next(res)//递归
},reject)//出错就跳出
}else{
resolve(data)//停止执行,返回的promse的对象调用.then()获取值
}
}
next()//第一次执行
})
}
co(read()).then(res=>{
console.log(res)//哈哈哈
})
【迭代器接口】
拓展:给object对象包装迭代器iterator接口
1】使用for of
循环必须要有iterator
迭代器接口
比如Map、Set、Array数组、String、generator函数、arrayLike类似数组的对象(但是必须要添加迭代的功能)、TypedArray等
非常注意:object对象没有迭代器接口,不能直接使用for of循环
var student={
name:'wujunchuan',
age:22,
locate:{
country:'china',
city:'xiamen',
school:'XMUT'
}
}
for(let key of Object.keys(student)){
//使用Object.keys()方法获取对象key的数组
console.log(key+": "+student[key]);
}
如果想要为对象object添加一个迭代器接口我们需要使用[Symbol.iterator]
:
ES5:(其实上面提到的数组、Map、Set等都是这样使用[Symbol.iterator]包装的迭代器)
//如为对象添加Iterator 接口;
let obj = {
name: "XX",
age: 20,
job: 'teacher',
[Symbol.iterator]() {
const self = this;
const keys = Object.keys(self);//是一个存键的数组
let index = 0;//使用计数器
return {
next() {
if (index < keys.length) {
return {
value: self[keys[index++]],
done: false
};
} else {
return { value: undefined, done: true };
}
}
};
}
};
for(let item of obj) {
console.log(item); //XX 20 teacher
}
ES6:使用Generator包装一个迭代器
let obj = {
name: "XX",
age: 20,
job: 'teacher',
* [Symbol.iterator] () {
const self = this;
const keys = Object.keys(self);
for (let index = 0;index < keys.length; index++) {
yield self[keys[index]];
}
}
};
2】只要是部署了 Iterator
迭代器接口的数据结构,Array.from
都能将其转为数组。类似数组的对象也可以被Array.from
直接转换成一个数组
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
//Array.from()会为这个类似数组的对象添加迭代功能
let arr3=[...arrayLike]//TypeError: arrayLike is not iterable因为对象没有迭代器接口,转出数组的话需要添加一个迭代器接口才行
let obj4={...arrayLike}//{ '0': 'a', '1': 'b', '2': 'c', length: 3 }
//所以我们就必须给这个arrayLike对象添加一个迭代器接口,
//才能使用[...arrayLike]来转换成真正的数组
let arrayLike = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator](){//Symbol元编程,就是可以更改js的行为(这里就是更改了js对象的功能)
//迭代器默认是一个对象,具备 next方法 和 调用过后返回的 value和done 的属性
const self = this
const keys = Object.keys(self);//是一个存键的数组
let index = 0
return {
next() {
return {
value:index < keys.length ? self[keys[index++]] : undefined,
done:index < keys.length ? false : true
};
}
}
}
}
// 对象中[Symbol.iterator]()方法必须返回一个对象,而且这个对象中必须要有next()这个方法,这个next()方法需要返回一个对象{value:xx,done:xx}
//迭代器是使用:
let x=arrayLike[Symbol.iterator]()
console.log(x.next());//{ value: 'a', done: false }
console.log(x.next());//{ value: 'b', done: false }
console.log(x.next());//{ value: 'c', done: false }
console.log(x.next());//{ value: 3, done: true }
//下面的扩展运算符相当于使用了for..of,只会执行返回done:false
console.log([...arrayLike]);//[ 'a', 'b', 'c' ]