基本概念

Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同。Generator函数有多种理解角度。语法上,首先可以把它理解成,Generator函数是一个状态机,封装了多个内部状态。
执行Generator函数会返回一个遍历器对象,也就是说,Generator函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历Generator函数内部的每一个状态。
形式上,Generator函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是产出)。

yield表达式解释

1.语法。
[rv] = yield [expression];
expression:定义通过迭代器协议从生成器函数返回的值。如果省略,则返回undefined。
rv:返回传递给生成器的next()方法的可选值,以恢复其执行。
2.描述
yield关键字使生成器函数执行暂停,yield关键字后面的表达式的值返回给生成器的调用者。它可以被认为是一个基于生成器的版本return关键字。
yield关键字实际返回一个IteratorResult对象,它有两个属性,value和done。value属性是对yield表达式求值的结果,而done是false,表示生成器函数尚未完全完成。

*yield,导致生成器再次暂停并返回生成器的新值。下一次调用next()时,在yield之后紧接着的语句继续执行。
*throw用于从生成器中抛出异常。这让生成器完全停止执行,并在调用者中继续执行,正如通常情况下抛出异常一样。
*到达生成器函数结尾;在这种情况下,生成器的执行结束,并且IteratorResult给调用者返回undefined并且done为true。
*到达return语句。这种情况下,生成器的执行结束,并将IterratorResult返回给调用者,其值是由return语句指定的,并且done为true。

如果将可选值传递给生成器的next()方法,则该值将称为生成器当前yield操作返回的值。
在生成器的代码路径中yield运算符,以及通过将其传递给Generator.prototype.next()指定新的起始值的能力之间,生成器提供了强大的控制力。
3.实例.

function*countAppleSales(){var arr = [1,2,3];for (var i=0;i<arr.length;i++){yield arr[i];}
}
//一旦生成器函数已定义,可以通过构造一个迭代器来使用它。
var appleStore = countAppleSales();//Generator{}
console.log(appleStore.next());//{value:1,done:false}
console.log(appleStore.next());//{value:2,done:false}
console.log(appleStore.next());//{value:3,done:false}
console.log(appleStore.next());//{value:undefined,done:true}//实例2let go = function*(x){console.log('x',x);let a = yield x;console.log('xx',x);console.log('a',a);let b = yield(x+1)+a;yield a+b;console.log('a+b',a+b);return a+b;
};
let g = go(10);
//执行到let a = yield x;
console.log(g.next());//x,10;{value:10,done:false};
//next(100)中的100作为第一个yield操作的返回值,即a=100;执行到let b = yield(x+1)+a;停止。
console.log(g.next(100));//x,10;{value:10,done:false};xx,10;a,100;{value:111,done:false};
//执行到yield a+b。g.next(10)中的10作为第二个yield操作的返回值,即b=10;
console.log(g.next(10));//x,10;{value:10,done:false};xx,10;a,100;{value:111,done:false};{value:110,done:false}
//所有都执行完毕。执行到return;
console.log(g.next());//x,10;{value:10,done:false};xx,10;a,100;{value:111,done:false};{value:110,done:false};a+b=110;{value:110,done:true};

Generator函数可以不用yield表达式,这时就变成了一个单纯的暂缓执行函数。另外需要注意,yield表达式只能用在Generator函数里面,用在其它地方都会报错。

function*f(){console.log('执行了!');
}
var generator = f();
generator.next();
//上面代码中,函数f如果是普通函数,在为变量generator赋值时就会执行。但是,函数f是一个Generator函数,就变成只有调用next方法时才会执行。

下面是另一个例子:

var arr=[1,[[2,3],4],[5,6]];
var flat = function*(a){a.forEach(function(item){if(typeof item!=='niumber'){yield*flat(item);}else{yield item;}});
}
for (var f of flat(arr)){console.log(f);
}

上面代码也会产生语句错误,因为forEach方法的参数是一个普通函数,但是在里面使用了yield表达式。一种修改方法是改用for循环。

var arr=[1,[[2,3],4],[5,6]];
var flat = function*(a){var length = a.length;for(var i=0;i<length;i++){var item = a[i];if(typeof item!='number'){//如果item不是number会把当前item为参数继续执行flat方法,直到转为number,最后的结果是yield1-yield7;yield*flat(item);}else{yield item;}}
};
var b =[];
//Generator可以使用for of方法遍历。
for(var f of flat(arr)){console.log(f);//1,2,3,4,5,6b.push(f);
}
console.log(b);//[1,2,3,4,5,6]

另外,yield表达式如果用在另一个表达式中,必须放在圆括号里面。

function*demo(){console.log('Hellow'+yield);//SyntaxErrorconsole.log('Hellow'+yield 123);//SyntaxErrorconsole.log('Hellow'+(yield));//okconsole.log('Hellow'+(yield 123));//ok
}

yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号。

function*demo(){foo(yield 'a',yield 'b');//oklet input = yield;//ok
}

与Iterator接口的关系

任意一个对象的symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数会返回一个遍历器对象。由于Generator函数就是遍历器生成函数,因此可以把Generator赋值给对象的symbol.iterator属性,从而使得该对象具有Iterator接口。

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

上面代码中,Generator函数赋值给Symbol.iterator属性,从而使得myIterable对象具有了Iterator接口,可以被...运算遍历了。
Generator函数执行后,返回一个遍历器对对象。该对象本身也具有Symbol.iterator属性,执行后返回自身。

function* gen(){// some code
}
var g = gen();
g[Symbol.iterator]() === g
// true

上面代码中,gen是一个Generator函数,调用它会生成一个遍历器对象g。它的Symbol.iterator属性,也是一个遍历器对象生成函数,执行后返回它自己。

next方法的参数

yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

function*f(){for(var i=0;true;i++){var reset = yield i;if(reset){i=-1}}
}
var g = f();
g.next()//{value:0,done:false}
g.next()//{value:1,done:false}
g.next(true)//{value:0,done:false}

上面代码先定义了一个可以无限运动的Generator函数f,如果next方法没有参数,每次运行到yield表达式,变量reset的值总是undefined。当next方法带一个参数true时,变量reset就被重置为这个参数(即true),因此i会等于-1,下一轮循环就会从-1开始递增。
这个功能有很重要的语法意义。Generator函数从暂停状态恢复运行,它的上下文状态时不变的。通过next方法的参数,就有办法在Generator函数开始运行之后,继续向函数内部注入值。也就是说,可以在Generator函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。
再看一个例子。

function*foo(x){var y = 2*(yield(x+1));var z = yield(y/3);return (x+y+z);
}
// var a = foo(5);
// console.log(a.next());//{value:6,done:false};
// console.log(a.next());//{value:NaN,done:false}因为此时y是undefined
// console.log(a.next());//{value:NaN,done:true}var b = foo(5);
console.log(b.next());//{value:6,done:false};
console.log(b.next(12));//{value:8,done:false} 此时y=2*第一个yield的返回值(12)。
console.log(b.next(13));//z=13,y=24,x=5.{ value:42, done:true }

上面代码中,第二次运行next方法的时候不带参数,导致y的值等于2*undefined(即NaN),除以3以后还是NaN,因此返回对象的value属性也等于NaN。第三次运行next方法的时候不带参数,所以z等于undefined,返回对象的value属性等于5+NaN+undefined,即NaN。
如果向next方法提供参数,返回结果就完全不一样了。上面代码第一次调用b的next方法时,返回x+1=6;第二次调用next方法,将上一次yield表达式的值设为12,因此y等于24,返回y/3的值8;第三次调用next方法,将上一次yield表达式的值设为13,因此z等于13,这时x等于5,y等于24,所以return语句的值等于42.
注意:由于next方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的。V8引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。
再看一个通过next方法的参数,向Generator函数内部输入值的例子。

function*dataConsumer(){console.log('Started');console.log(`1. ${yield}`);console.log(`2. ${yield}`);return 'result';
}
let genObj = dataConsumer();
function* dataConsumer(){console.log('Started');console.log(`1. ${yield}`);console.log(`2. ${yield}`);return 'result';
}
alet genObj = dataConsumer();
genObj.next();
//Started
genObj.next('a');
//1.a
genObj.next('b');
//2.b

上面代码是一个很直观的例子,每次通过next方法向Generator函数输入值,然后打印出来。
如果想要第一次调用next方法时,就能够输入值,可以在Generator函数外面再包一层。

function wrapper(generatorFunction){return function(){let generatorObject = generatorFunction();generatorObject.next();return generatorObject;};
}
const wrapped = wrapper(function*(){console.log(`First input: ${yield}`);return 'DONE';
});
wrapped().next('hello!');
// console.log(wrapped().next('hello!'))

上面代码中,Generator函数如果不用wrapper先包一层,是无法第一次调用next方法,就输入参数的。

for...of循环

for...of循环可以自动遍历Generator函数时生成Iterator对象,且此时不再需要调用next方法。

function* foo() {yield 1;yield 2;yield 3;yield 4;yield 5;return 6;
}for (let v of foo()) {console.log(v);
}
// 1 2 3 4 5

上面代码使用for...of循环,依次显示5个yield表达式的值。这里需要注意,一旦next方法的返回对象的done属性为true,for...of循环就会中止,且不包含该返回对象,所以上面代码的return语句返回的6,不包括在for...of循环中。
下面是一个利用Generator函数for...of循环,实现斐波那契数列的例子。

function*fibonacci(){let [prev,curr] = [0,1];for(;;){yield curr;[prev,curr] = [curr,prev+curr];}
}
for(let n of fibonacci){if(n>100)break;console.log(n);
}

利用for...of循环,可以写出遍历任意对象的方法。原生JavaScript对象没有遍历接口,无法使用for...of循环,通过Gneerator函数为它加上这个接口,就可以用了。

//Reflect.ownKeys()方法返回target对象自己的属性键的数组。
//Reflect.ownKeys({z:3,y:2,x:1});//['z','y','x'];
//Reflect.ownKeys([]);//["length"];
//Reflect.ownKeys([1,2,3,4]);//["1","2","3","4","length"]
function*objectEntries(obj){let propKeys = Reflect.ownKeys(obj);for(let propKey of propKeys){yield [propKey,obj[propKey]]}
}
let jane = {first:'jane',last:'Doe'};
for(let[key,value] of objectEntries(jane)){console.log(`${key}:${value}`);
}
// first: Jane
// last: Doe

上面代码中,对象jane原生不具备 Iterator 接口,无法用for...of遍历。这时,我们通过 Generator 函数objectEntries为它加上遍历器接口,就可以用for...of遍历了。加上遍历器接口的另一种写法是,将 Generator 函数加到对象的Symbol.iterator属性上面。

//Object.keys 返回一个所有元素为字符串的数组,其元素来自于从给定的object上面可直接枚举的属性。这些属性的顺序与手动遍历该对象属性时的一致。
// simple array
var arr = ['a', 'b', 'c'];
console.log(Object.keys(arr)); // console: ['0', '1', '2']// array like object
var obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.keys(obj)); // console: ['0', '1', '2']// array like object with random key ordering
var anObj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.keys(anObj)); // console: ['2', '7', '100']// getFoo is a property which isn't enumerable
var myObj = Object.create({}, {getFoo: {value: function () { return this.foo; }}
});
myObj.foo = 1;
console.log(Object.keys(myObj)); // console: ['foo']/**/
function* objectEntries() {let propKeys = Object.keys(this);for (let propKey of propKeys) {yield [propKey, this[propKey]];}
}let jane = { first: 'Jane', last: 'Doe' };
jane[Symbol.iterator] = objectEntries;for (let [key, value] of jane) {console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe

除了for...of循环以外,扩展运算符(...)、解构赋值和Array.from方法内部调用的,都是遍历器接口。这意味着,它们都可以将 Generator 函数返回的 Iterator 对象,作为参数。

function* numbers () {yield 1yield 2return 3yield 4
}// 扩展运算符
[...numbers()] // [1, 2]// Array.from 方法
Array.from(numbers()) // [1, 2]// 解构赋值
let [x, y] = numbers();
x // 1
y // 2// for...of 循环
for (let n of numbers()) {console.log(n)
}
// 1
// 2

Generator函数的语法以及异步的应用相关推荐

  1. ES6学习(九)—Generator 函数的语法

    ES6学习(九)-Generator 函数的语法 Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同. Generator函数是一个状态机,内部封装了不同状态的 ...

  2. “睡服”面试官系列第十八篇之generator函数的语法(建议收藏学习)

    目录 1简介 1.1基本概念 1.2yield 表达式 1.3与 Iterator 接口的关系 2. next 方法的参数 3. for...of 循环 4. Generator.prototype. ...

  3. ES6——Generator 函数的语法

    Generator 函数是一个状态机,封装了多个内部状态.执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数.返回的遍历器对象 ...

  4. es6中的generator函数详解

    Generator 函数的定义 语法上,Generator 函数是一个状态机,封装了多个内部状态. 形式上,Generator是一个函数.不同于普通函数,是可以暂停执行的,所以函数名之前要加星号,以示 ...

  5. generator函数详解

    Generator 函数的定义 语法上,Generator 函数是一个状态机,封装了多个内部状态. 形式上,Generator是一个函数.不同于普通函数,是可以暂停执行的,所以函数名之前要加星号,以示 ...

  6. JavaScript 异步编程--Generator函数、async、await

    JavaScript 异步编程–Generator函数 Generator(生成器)是ES6标准引入的新的数据类型,其最大的特点就是可以交出函数的执行的控制权,即:通过yield关键字标明需要暂停的语 ...

  7. 【JavaScript】Generator函数

    目录 一.简介 二.基本用法 三.next传参 四.Generator函数异步应用 一.简介 Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同. Genera ...

  8. ES6语法总结(21)--Generator函数的异步应用

    Generator 函数的异步应用 传统方法 基本概念 Generator 函数 Thunk 函数 co 模块 异步编程对 JavaScript 语言太重要.JavaScript 语言的执行环境是&q ...

  9. Generator函数语法

    Generator函数语法 基础概念 *星号的位置 next方法的参数 tips 基础概念 Generator 函数是 ES6 提供的 种异步编程解决方案,语法行为与传统函数完全不同. 执行 Gene ...

最新文章

  1. 2018CTDC风暴来袭乌镇 互联网大佬再续前缘
  2. 《DSP using MATLAB》示例Example7.22
  3. 深度学习的数学 (6)误差反向传播法必需的链式法则
  4. Python实现网页截图
  5. spring循环依赖及解决方法
  6. JDBC的CRUD操作之PreparedStatement的修改操作
  7. eclipse自动补全
  8. mysql主从搭建教程
  9. 常用网络协议的端口号
  10. 5.2探究执行器(Executors)
  11. 51nod 1526 分配笔名(字典树+贪心)
  12. FRM-92120: Registry.dat
  13. 【重要】股票收益互换
  14. win10更新后耳机没有声音的解决方式
  15. rockchip 瑞芯微 SDK 一些解释
  16. 拷机测试需要多久_软件测试培训多久能学会?这六个阶段是要有的
  17. Oasis Sapphire黑客松来袭 | 构建隐私DApp,赢取9000美元奖励!
  18. 文本prompting综述
  19. tws耳机哪个品牌好?2023年tws耳机排行
  20. tabindex 属性 - HTML中代表使用Tab键的遍历顺序

热门文章

  1. 面试常用shell脚本_Shell脚本编写及常见面试题
  2. java序列化库_java 中序列化(Serializable)
  3. 中考考试的指令广播_明天FM105.2《朝朝早精神好》推出2017广州中考日特别报道...
  4. mysql2008jar包下载_求java jdbc 连sql server2008的jar包
  5. php彩票平台,直播详情-阿森纳vs热刺-英超 20/21
  6. c语言将一个已知头结点的单链表逆序_C语言数据结构实现链表逆序并输出
  7. 离散傅里叶变换(DFT)(为了使用而学习的DFT)
  8. 10-GLBP Weighting //2.1.5(GNS3版本,后面都是如此注明)
  9. 统计上报---日志上报成功率高的方式
  10. Docker学习笔记 — Docker私有仓库搭建