Generator生成器是ES6标准引入的新的数据类型,一个生成器看上去像是一个函数但可以返回多次。
Generator生成器是ES6引入的,用于异步编程,最大特点是可以交出函数的执行权(暂停执行),暂停执行主要是通过
yield
来实现。Generator生成器是一个状态机,封装了多个内部状态。
Generator生成器本质上是一个封装的异步任务
普通函数
普通函数调用时会先传入参数然后返回结果,函数执行过程中,如果没有遇到return
语句,函数的控制权是无法交回给被调用的代码的。
//普通函数
function fn(){
console.log("normal function");
}
fn();//normal function
生成器
生成器定义
//生成器
function* generator(){
yield 1;
yield 2;
yield 3;
return undefined;
}
let iter= generator();//创建函数句柄并未实际执行
console.log(iter);//iter{<suspended>}
let obj1 = iter.next();
console.log(obj1);//{value: 1, done: false}
let obj2 = iter.next();
console.log(obj2);//{value: 2, done: false}
let obj3 = iter.next();
console.log(obj3);//{value: 3, done: false}
let obj4 = iter.next();
console.log(obj4);//{value: undefined, done: true}
注意:每次调用next()
方法会返回一个对象,表示当前阶段的信息,对象包括两个属性,value
属性表示返回的值,done
布尔值表示函数执行是否结束。
-
function
关键字与函数名之间有一个星号*
星号的作用是用于标识不同于普通函数,表示生成器是可以暂停执行的。
- 生成器的函数体内部使用
yield
语句可以定义不同的内部状态
所谓的状态实际就是数据,函数内部的状态实际是指函数内部的值,在不同时函数内部的值是不同的。
生成器与普通函数的区别
生成器与普通函数的区别在于,生成器由function*
定义,它除了return
返回语句外,还可以使用yield
命令返回多次。
//生成器
function* generator(){
yield 1;
yield 2;
yield 3;
return undefined;
}
let iter= generator();
while((item = iter.next()).done === false){
console.log(item.value);
}
生成器函数表达式
可以通过函数表达式来创建生成器,只需要在function
关键字和小括号之间添加星号*
即可。
let generator = function* (items){
for(let i=0; i<items.length; i++){
yield items[i];
}
};
let iterator = generator([1,2,3]);
for(let item of iterator){
console.log(item);
}
生成器对象
由于生成器本身就是函数,因此可以将其添加到对象中。
使用ES5对象字面量通过函数表达式创建生成器
var obj = {
generator:function *(items){
for(let i=0; i<items.length; i++){
yield items[i];
}
}
};
let iter = obj.generator([1,2,3]);
for(let item of iter){
console.log(item);
}
使用ES6函数方法简写方式创建生成器,只需在函数名前添加星号。
let obj = {
*generator(items){
for(let i=0; i<items.length; i++){
yield items[i];
}
}
};
let iter = obj.generator([1, 2, 3]);
for(let item of iter){
console.log(item);
}
利用生成器作为状态机
let state = function* (){
while(true){
yield 0;
yield 1;
yield 2;
}
};
let status = state();
console.log(status.next().value);//0
console.log(status.next().value);//1
console.log(status.next().value);//2
yield 关键字
yield
是生成器内部的暂缓执行的标识,yield
只能配合生成器使用,普通函数中使用则会报错。
function* generator(items){
items.forEach((item)=>{
yield item;//Uncaught SyntaxError: Unexpected identifier
});
}
yield
关键字可以返回任何值或表达式,可以通过生成器批量给迭代器添加元素。
function* generator(items){
for(let i=0; i<items.length; i++){
yield items[i];
}
}
let iter = generator([1,2,3]);
for(let item of iter){
console.log(item);// 1 2 3
}
yield
只能原封不动地返回右边运算后的值
let generator = function* (){
yield 0;
yield 1+1;
yield gen();//yield只能原封不动的返回右边运算后的值
};
let gen = function* (){
yield 10+1;
yield 20;
};
var iter = generator();
console.log(iter.next().value);//0
console.log(iter.next().value);//2
console.log(iter.next().value);//gen {<suspended>}
使用yield*
可以让其自动遍历该对象
let generator = function* (){
yield 0;
yield 1+1;
yield* gen();
};
let gen = function* (){
yield 10+1;
yield 20;
};
var iter = generator();
console.log(iter.next().value);//0
console.log(iter.next().value);//2
console.log(iter.next().value);//11
yield
表达式的返回值
let generator = function* (arg){
console.log(arg);
let ret = yield arg;
console.log(ret);
};
let iter = generator(1);
console.log(iter.next());
// 1
// {value: 1, done: false}
console.log(iter.next());
// undefined
// {value: undefined, done: true}
console.log(iter.next(2));
//{value: undefined, done: true}
生成器返回值
执行生成器会返回一个遍历器(迭代器Iterator
)对象,也就是说生成器除了是状态机还是一个遍历器生成函数,根据返回的遍历器对象可依次遍历生成器内部的每个状态。
调用生成器后函数并不执行,返回的不是函数运行结果,而是指向内部引用状态的指针对象,也就是遍历器对象。
因此必须调用遍历器对象的next()
方法,使得指针移向下一个状态。也就是说,每次调用next()
方法内部指针会从函数头部或上次停止的位置开始执行,直到遇到下一个yield
表达式或return
语句为止。换言之,生成器是分段执行的,yield
表达式只是暂停的标志,next()
方法可以恢复执行。
实现了迭代器接口的对象都可以使用for...of
实现遍历
function* generator(){
let i = 0;
yield ++i;
yield ++i;
yield ++i;
return undefined;
}
//生成器函数执行后返回的是一个迭代器对象
let iterator = generator();
//迭代器可使用for...of遍历
for(let item of iterator){
console.log(item);//1 2 3
}
迭代器
迭代器是一种特殊的对象,具有专门为迭代过程设计的专用接口。
- 所有的迭代对象都具有一个
next()
方法,每次调用时都回返回一个结果对象。结果对象包含两个属性,一个是value
表示下一个将要返回的结果,另一个是done
表示一个布尔类型的值。但没有更多可返回数据时done
会返回true
。 - 迭代器还会保存一个内部指针,用来指向当前集合中值的位置,每次调用
next()
方法都回返回下一个可用的值。
如果在最后一个值返回后再调用next()
方法返回的对象中属性done
的值将为true
,属性value
则包含迭代器最终返回的值,这个返回值不是数据集的一部分,它会函数的返回值类似,是函数调用过程中最后一次给调用者传递信息的方法,如果没有相关数据则返回undefined
。
例如:ES5中创建迭代器的方式
//ES5创建迭代器
function createIterator(items){
var i = 0;
return {
next:function(){
var done = i >= items.length;
var value = done ? undefined : items[i++];
return {value:value, done:done};
}
};
}
var iter = createIterator([1, 2, 3]);
console.log(iter);//{next: ƒ}
console.log(iter.next());//{value: 1, done: false}
console.log(iter.next());//{value: 2, done: false}
console.log(iter.next());//{value: 3, done: false}
console.log(iter.next());//{value: undefined, done: false}
生成器使用
- 通过
yield
标识位和next()
方法调用,可以实现函数的分段执行。 -
yield
命令是异步不同阶段的分界线,是生成器暂停执行的标识。 -
yield
只能配合生成器使用 - 生成器执行时需使用
next()
方法,next()
可以理解为启动方法,作用是分阶段执行生成器,每次调用next()
方法会返回一个对象,该对象表示生成器当前阶段的信息。
例如:使用普通函数获取斐波拉契数列
function fib(max){
let tmp, x = 0, y = 1, ret = [];
while(ret.length < max){
[x, y] = [y, x+y];
ret.push(y);
}
return ret;
}
console.log(fib(5));// [1, 2, 3, 5, 8]
例如:使用生成器产生斐波拉契数列
//定义生成器
function* fib(max){
let tmp, count = 0, x = 0, y = 1;
while(count < max){
yield x;
[x, y] = [y, x+y];
count++;
}
return;
}
let gen = fib(5);//创建生成器对象
// 生成器的next()方法会执行函数体内的代码
// 每次遇到yield就会返回一个对象{value:x, done:bool}并暂停
// 返回的value是yield的返回值,done则表示生成器是否已经执行结束。
// 如果done的值为true,则此时value就是生成器函数体内return的值。
console.log(gen.next());//{value: 0, done: false}
console.log(gen.next());//{value: 1, done: false}
console.log(gen.next());//{value: 1, done: false}
console.log(gen.next());//{value: 2, done: false}
console.log(gen.next());//{value: 3, done: false}
console.log(gen.next());//{value: undefined, done: true}
生成器的作用
由于生成器可以在执行过程中多次返回,所以看上其就像一个可以记住执行状态的函数。利用这一点,编写生成器可以实现需要面向对象才能实现的功能。
例如:使用对象来保存状态
let fib = {
x:0,
y:1,
count:0,
max:5,
next:function(){
let result = this.x;
let tmp = this.x + this.y;
this.x = this.y;
this.y = tmp;
if(this.count < this.max){
this.count++;
return result;
}else{
return undefined;
}
}
};
console.log(fib.next());//0
console.log(fib.next());//1
console.log(fib.next());//1
console.log(fib.next());//2
console.log(fib.next());//3
console.log(fib.next());//undefined