ES6笔记 - Generator函数

Generator 是一个分步执行的函数,可以根据需要执行下一步。合理利用这个特性可以很好解决异步编程回调嵌套的问题。

简介

Generator很像是一个函数,但是你可以暂停它的执行。你可以向它请求一个值,于是它为你提供了一个值,但是余下的函数不会自动向下执行直到你再次向它请求一个值。

特征

  • function 后面跟 *,函数体内有 yield 关键字
  • 执行Genertor函数返回一个遍历器对象,遇到 yield 暂停执行,调用 next 继续执行
  • next 方法返回的对象中 value 表示当前指针对应的yield语句的返回值,done 表示遍历是否结束
  • 当遍历结束之后,重复调用都只会返回 {value: undefined, done: true}
  • 如果函数有 return ,则返回 return 后面表达式的值做为 value的值,如果没有,则返回undefined 做为 value 的值
     
     
function* fn1(){
yield 'a'
yield 'b'
return 'c';
}
var g1 = fn1();
g1.next(); //{value: "a", done: false}
g1.next(); //{value: "b", done: false}
g1.next(); //{value: "c", done: false}
g1.next(); //{value: undefined, done: true}

多维数组扁平化

     
     
var arr = [ 1, [[ 2, 3], 4], [ 5, 6]];
var flat = function* (a){
var len = a.length, item;
for( var i= 0; i<len; i++){
item = a[i];
if( typeof item !== 'number'){
yield* flat(item);
} else{
yield item;
}
}
}
var g2 = flat(arr);
g2.next() //{value: 1, done: false}
for( var f of g2){
console.log(f)
}
// 2,3,4,5,6

Iterator接口

任意一个对象的 Symbol.iterator 方法,等于该对象的遍历器生成函数
将 Generator 函数赋值给对象的 Symbol.iterator 属性,可使对象具有Iterator接口
具有Iterator接口的对象可使用 ... 延展符展开

     
     
var k = {};
k[ Symbol.iterator] = function* (){
yield 1;
yield 2;
}
[...k] //[1, 2]

Generator 函数返回的遍历器对象也有Symbol.iterator对象,执行后返回自身

     
     
var m = function* (){}
var g3 = m();
g3[ Symbol.iterator]() == g3 //true

yield 语句本身没有返回值,或者永远返回 undefined
next 方法有参数时,参数将会被当然上一个 yield 语句的返回值

     
     
function* f() {
for( var i= 0; true; i++) {
//这里,如果next没有传参数过来,reset将一直是undefined
var reset = yield i;
console.log( typeof reset)
if(reset) { i = -1; }
}
}
var g = f();
g.next(); //{value: 0, done: false}
g.next(); //undefined {value: 1, done: false}
g.next( true); //boolean {value: 0, done: false}

Generator函数在运行时,内部的 上下文(context )是保持不变的,但可以通过 next 方法不但的向函数体注入参数,从而改变函数的行为。
next 方法第一次调用时的参数会被忽略,只有后面的传参会被使用,第一个next方法可视作是启动遍历器对象

     
     
function* foo(x) {
var y = 2 * ( yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
//这里,由于yield本身的返回值是undefined,所以导致后面的计算都为NaN
var a = foo( 5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
//这里,第二次的next传递了参数12,y=24, z=24/3=>8
//第三次 x=5 z=13 y=24 故x+y+z = 42
var b = foo( 5);
b.next() // { value:6, done:false }
b.next( 12) // { value:8, done:false }
b.next( 13) // { value:42, done:true }

for...of 遍历

可以使用 for...of 遍历Generator 对象,不需要调用 next

     
     
function* fn(){
yield 1;
yield 2;
yield 3;
return 4;
}
for( var i of fn()){
console.log(i)
}
//1,2,3

由于 for...of 遍历时,当返回的对象 done 为 true 时就终止了循环,且不返回对象的 value, 所以最后的 4 没有输出,执行到 return 返回的是 {value: 4, done: true}

例1:利用Generator函数和for…of循环,实现斐波那契数列

     
     
function* fibonacci(){
let [prev, curr] = [ 0, 1];
for(;;){
[prev, curr] = [curr, prev+curr];
yield curr;
}
}
for( var y of fibonacci()){
if(y> 50) break;
console.log(y);
}
// 1, 2, 3, 5, 8, 13, 21, 24

例2:给原生对象添加Iterator遍历接口

     
     
function* objectEntries(){
let keys = Object.keys( this);
for( let key of keys){
yield [key, this[key]]
}
}
var obj = { name: 'jack', age: 12, city: 'hangzhou'}
obj[ Symbol.iterator] = objectEntries;
for( let [key, value] of obj){
console.log( `${key}: ${value}`)
}
//name: jack
//age: 12
//city: hangzhou

for...of循环 / 扩展运算符... / 解构赋值 / Array.from 都可以操作Generator函数返回的Iterator对象

     
     
function* numbers(){
yield 1;
yield 2;
return 3;
yield 4;
}
[...numbers()]
//[1, 2]
Array.from(numbers())
//[1, 2]
let [x, y] = numbers()
//[1, 2]
for( let n of numbers()){
console.log(n)
}
//1, 2

Generator.prototype.throw

Generator函数内部部署 try...catch 可以对多个 yield 语句进行错误捕捉
Generator函数内部抛出错误可以被函数体外catch捕获
Generator函数体外抛出错误可以被函数体内catch捕获

     
     
function* fnA(){
let x = 1;
try{
yield console.log( 'a')
yield console.log( 'b')
//这里报错会中断后面的执行,返回{value: undefined, done: true}
yield x.toUppercase()
yield console.log( 'd')
} catch(e){
console.log( '内:', e)
throw e
}
}
var ga = fnA()
try{
ga.next();
//外面的错误会被内部catch捕获
ga.throw( '在函数体外部抛出的错误')
} catch(e){
console.log( '外:', e)
}
//由于内部中断了执行,再调用next只会拿到{value: undefined, done: true}的结果
ga.next()
//输出结果:
//a
//内: 在函数体外部抛出的错误
//外: 在函数体外部抛出的错误
//{value: undefined, done: true}

如果Generator函数返回的Iterator对象在外部抛出了错误,而函数体内部又没有捕获,那么这个错误将抛给全局,程序中断

     
     
var gen = function* gen(){
yield console.log( 'hello');
yield console.log( 'world');
}
var g = gen();
g.next();
g.throw();
// hello
// Uncaught undefined 报错

Generator函数返回的Iterator对象throw一个错误后,会同时调用一次next方法

     
     
var gen = function* gen(){
try {
yield console.log( 'a');
} catch (e) {
// ...
}
yield console.log( 'b');
yield console.log( 'c');
}
var g = gen();
g.next() // a
g.throw() // b
g.next() // c

Generator.prototype.return

遍历器对象调用return可以改变返回的结果的vaule,并结束遍历返回的done为true
如果Generator函数内部有try…finally代码块,那么return方法会推迟到finally代码块执行完再执行

     
     
function* numbers () {
yield 1;
try {
yield 2;
yield 3;
} finally {
yield 4;
yield 5;
}
yield 6;
}
var g = numbers()
g.next() // { done: false, value: 1 }
g.next() // { done: false, value: 2 }
g.return( 7) // { done: false, value: 4 }
g.next() // { done: false, value: 5 }
g.next() // { done: true, value: 7 }

yield*

用于在一个Generator函数里面执行另一个Generator函数

     
     
function* fn1(){
yield 4;
yield 5;
}
function* fn2(){
yield 1;
yield 2;
yield 3;
yield* fn1();
yield 6;
}
function* fn3(){
yield 1;
yield 2;
yield fn1();
yield 6;
}
[...fn2()] //[1, 2, 3, 4, 5, 6] 将fn1中的内容展开了
[...fn3()] //[1, 2, fn1, 6] fn1为Generator返回的iterator对象

yield* 在Generator函数中可以遍历任何具有Iterator接口的对象

     
     
function* fn4(){
yield* [ 1, 3, 4]
yield* 'Hllo'
}
[...fn4()] //[1, 3, 4, "H", "l", "l", "o"]

yield 后面的Generator函数中可以通过 return 返回数据给 yield 所在的Generator函数

     
     
function* fn5(){
yield 1;
yield 2;
return 3;
}
function* fn6(){
yield 4;
var res = yield* fn5();
yield res;
yield 5;
}
[...fn6()] //[4, 1, 2, 3, 5]

多维数组转一维数组

     
     
function* flatArr(arr){
if( Array.isArray(arr)){
for( var k = 0; k<arr.length; k++){
yield* flatArr(arr[k])
}
} else{
yield arr;
}
}
var arr1 = [ 1,[ 2, 3,[ 4, 5, 6]], 7,[ 8, 9]]
for( var i of flatArr(arr1)){
console.log(i)
}
// 1,2,3,4,5,6,7,8,9

二叉树遍历

     
     
function Tree(left, label, right){
this.left = left;
this.label = label;
this.right = right;
}
function make(arr){
if(arr.length === 1) return new Tree( null, arr[ 0], null);
return new Tree(make(arr[ 0]), arr[ 1], make(arr[ 2]))
}
function* inorder(t){
if(t){
yield* inorder(t.left)
yield t.label
yield* inorder(t.right)
}
}
var tree = make([[[ 'a'], 'b', [ 'c']], 'd', [[ 'e'], 'f', [ 'g']]]);
var res = [];
for( var k in inorder(tree)){
res.push(k)
}
console.log(res) //["a", "b", "c", "d", "e", "f", "g"]

做为对象属性

     
     
var obj = {
*fn(){
yield 1;
yield 2;
}
}
//等同于
var obj2 = {
fn: function* (){
yield 1;
yield 2;
}
}
[...obj.fn()] //[1, 2]
[...obj2.fn()] //[1, 2]

状态保持

     
     
function* tick(){
while( true){
yield 'Tick'
yield 'Tock'
}
}
var t = tick();
t.next();
t.next();
t.next();
t.next();

应用场景

异步操作同步化

使用嵌套回调

     
     
function asyncFn1(done){
setTimeout( ()=>{
console.log( 1);
done( 1);
}, 1500);
}
function asyncFn2(done){
setTimeout( ()=>{
console.log( 2);
done( 2);
}, 2000);
}
function asyncFn3(done){
setTimeout( ()=>{
console.log( 3);
done();
}, 2500);
}
asyncFn1( function(args1){
asyncFn2( function(args2){
asyncFn3( function(args3){
//...
})
})
})

使用Generator封装,可以简化回调,让异步写起来更像是同步

     
     
//使用done回调保证任务按顺序执行
function asyncFn1(done){
setTimeout( ()=>done( 1), 1500);
}
function asyncFn2(done){
setTimeout( ()=>done( 2), 2000);
}
function asyncFn3(done){
setTimeout( ()=>done( 3), 2500);
}
//co的简单实现
function co(task){
var gen = task();
next();
function next(res){
var ret;
ret = gen.next(res);
if(ret.done) return;
if( typeof ret.value === 'function'){
ret.value( function(){
//依次遍历执行,如果没遍历完成,则递归调用
next.apply( this, arguments)
})
return;
}
}
}
co( function* task(){
try{
var res1 = yield asyncFn1; //第1次 next fn1返回结果1
console.log(res1) //第2次 next 传递fn1的结果 1
var res2 = yield asyncFn2;
console.log(res2) //第3次 next 传递fn2的结果 2
var res3 = yield asyncFn3;
console.log(res3) //第4次 next 传递fn3的结果 3
} catch(e){
// ...
}
})
// 1, 2, 3

任务流程管理

依次执行数组中的步骤

     
     
var step1 = () => 1;
var step2 = () => 2;
var step3 = () => 3;
var steps = [ step1, step2, step3 ]
function* iterateSteps(steps){
for( var step of steps){
yield step()
}
}
[...iterateSteps(steps)] // [1, 2, 4]

将多个步骤组合成多个任务,并依次执行这多个任务

     
     
var step1 = () => 1;
var step2 = () => 2;
var step3 = () => 3;
var step4 = () => 4;
var step5 = () => 5;
var step6 = () => 6;
var step7 = () => 7;
var step8 = () => 8;
var job1 = [ step1, step2, step3 ]
var job2 = [ step4, step5, step6 ]
var job3 = [ step7, step8 ]
var jobs = [job1, job2, job3]
function* iterateSteps(steps){
for( var step of steps){
yield step()
}
}
function* iterateJobs(jobs){
for( var job of jobs){
yield* iterateSteps(job)
}
}
[...iterateJobs(jobs)] //[1, 2, 3, 4, 5, 6, 7, 8]

部署Iterator接口

给任意对象部署Iterator接口

     
     
function* iterEntries(obj) {
let keys = Object.keys(obj);
for ( let i= 0; i < keys.length; i++) {
let key = keys[i];
yield [key, obj[key]];
}
}
let myObj = { foo: 3, bar: 7 };
for ( let [key, value] of iterEntries(myObj)) {
console.log(key, value);
}

作为类数据结构

     
     
function doStuff(){
return [ 1, 2, 3]
}
for( var i of doStuff()){
console.log(i)
}
// 1, 2, 3
function* genStuff(){
yield 1;
yield 2;
yield 3;
}
for( var i of genStuff()){
console.log(i)
}
// 1, 2, 3
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值