generator(生成器)
什么是generator函数:
在js中,一个函数一旦执行,就会运行到最后或者遇到return时结束,运行期间不会有其它代码能打断它,也不能从外部再传入值到函数体内。
generator函数的出现就可以打断一个函数的完整运行,语法和传统函数完全不同。
generator是ES6提供的一种异步变成解决方案,形式上也是一个普通函数,但是有几个显著的特征:
1、function关键字和函数名之间有一个星号*,紧挨着function关键字
2、函数体内使用yield表达式,定义不同的内部状态,yield表达式可以有多个
3、直接调用generator函数并不会执行,也不会返回运行结果,而是返回一个遍历器对象(Iterator Object)
4、依次调用遍历器对象的next()方法,遍历generator函数内部的每一个状态
普通函数与generator函数的对比:
function fn(){
return "你好!";
}
console.log(fn()) // 你好! ----普通函数一旦调用立即执行 function* fn1(){
yield "你好"; // yield表达式是暂停执行的标记
return "hello";
}
var f=fn1(); // 调用generator函数,函数并没有执行,而是返回一个iterator对象
console.log(f.next()) // {value: "你好", done: false} ----value是返回值,done是一个布尔值,false表示遍历还没哟结束
console.log(f.next()) // {value: "hello", done: true} ----done: true表示遍历结束
console.log(f.next()) // {value: undefined, done: true} ----当done为true时,遍历已经结束,不会有返回值,继续下去依然打印这句
generator函数内部的执行像是被人推一下,动一步:
function* go(){
yield "走一步";
yield "再走一步";
return "结束";
}
let g=go();
console.log(g.next()); // {value: "走一步", done: false}
console.log(g.next()); // {value: "再走一步", done: false}
console.log(g.next()); // {value: "结束", done: true}
console.log(g.next()); // {value: undefined, done: true}
上面代码中定义了一个generator函数,其中包括两个yield表达式和一个rerun语句,即产生了三个状态。
每次调用iterator对象的next()方法时,内部的指针就会从函数的头部或上一次停下来的地方开始执行,知道遇到下一个yield表达式或return语句暂停。换句话说,generator函数是分段执行的,yield表达式是暂停执行的标记,而next()方法可以恢复执行。
执行过程:
1、第一次调用next()方法时,内部指针从函数头部开始执行,遇到第一个yield表达式时暂停,并返回当前的状态值“走一步”
2、第二次调用next()方法时,内部指针从上一个(这里是第一个)yield表达式开始,遇到第二个yield表达式暂停,返回当前的状态值“再走一步”
3、第三次调用next()方法时,内部指针从第二个yield表达式开始,遇到return语句暂停,返回当前状态的值“结束”。此时所有的状态遍历完毕,done属性的值变为true
4、第四次调用next()方法时,由于函数已经遍历完毕,没有其它状态,因此返回{value:undefined,done:true},如果继续调用next()方法,依然返回这个值
yield表达式:
1、yield表达式只能用在generator函数内,用在其它地方会报错
function fn(){
yield 111; // Uncaught SyntaxError: Unexpected number ----语法错误
}
fn();
2、yield表达式如果用在另一个表达式中,必须要放在小括号里,不加小括号会报错
function* demo(){
var str="hello"+(yield 123);
var str1="hello"+(yield 456);
return str+str1;
}
var d=demo();
console.log(d.next()) // {value: 123, done: false}
console.log(d.next()) // {value: 456, done: false}
console.log(d.next()) // {value: "helloundefinedhelloundefined", done: true}
console.log(d.next()) // {value: undefined, done: true}
3、yield表达式当做参数放在赋值表达式的右边,可以不加括号
function* demo(){
fn(yield "a",yield "b");
let input=yield;
}
4、yield与return的区别:
相似:都能返回紧跟在语句后的那个表达式的值
区别:每次遇到yield,函数就会暂停执行,下一次再从该位置继续向后执行,而return语句不具备记忆位置的功能。一个函数只能有一个return,而在generator函数中可以有多个yield。
yield*表达式:
如果在一个generator函数中调用另一个generator函数,是没有效果的:
function* fn(){
yield "aaa";
yield "bbb";
}
function* bar(){
fn();
yield "ccc";
yield "ddd";
}
let b=bar();
for(let value of b){
console.log(value) // ccc ddd
}
当前只输出bar函数自己的两个状态值,如果想要正确地在bar里调用fn函数,就需要用到yield*表达式。
yield*表达式用于在一个generator函数中调用另一个generator函数:
function* fn(){
yield "aaa";
yield "bbb";
}
function* bar(){
yield* fn();
yield "ccc";
yield "ddd";
}
let b=bar();
for(let value of b){
console.log(value) // aaa bbb ccc ddd
}
next()方法的参数:
yield表达式本身没有返回值,或者说返回值总是一个undefined。next()方法可以带一个参数,这个参数就会被当做是上一个yield表达式的返回值。
[rv] = yield [expression]
expression:定义通过遍历器从生成器函数返回的值,如果省略,则返回undefined
rv:接受从下一个next()方法传递来的参数
例子:
function* fn(){
let num=yield 1+2+3;
console.log(num)
yield num;
}
var f=fn();
console.log(f.next()) // {value: 6, done: false}
console.log(f.next()) // undefined {value: undefined, done: false}
console.log(f.next()) // {value: undefined, done: true}
第一次调用遍历器对象next()方法,函数从头部开始执行,遇到第一个yield暂停,着这个过程中其实是分了三步:
1、声明一个变量num,并将声明提前,默认值为undefined
2、由于generator函数是“惰性求值”,执行到第一个yield时才会计算求和,并将计算结果返回给遍历器对象{value: 6, done: false},函数暂停执行
3、理论上应该把等号右边的yield 1+2+3赋值给变量num,但是,由于函数执行到yield时暂停了,这一步就被挂起了
第二次调用next()方法,函数从上一次yield停下的地方开始执行,也就是给num赋值的地方开始,由于next()并没有传参,就相当于传参为undefined
基于以上分析,不难理解yield表达式本身的返回值([rv])总是undefined。现在当第二次调用next()方法时,传入参数3:
function* fn(){
let num=yield 1+2+3;
console.log(num)
yield num;
}
var f=fn();
console.log(f.next()) // {value: 6, done: false}
console.log(f.next(3)) // 3 {value: 3, done: false} ----yield表达式的返回值是undefined,当不传参数时,num接收到的是undefined;这里next()带的参数被当成上一个yield表达式的返回值
console.log(f.next()) // {value: undefined, done: true}
如果第一次调用next()的时候也传了一个参数呢?这是无效的,next()方法的参数表示上一个yield表达式的返回值,所以在第一次使用next()方法时,参数无意义,即第一次调用next()方法时,不传参。
generator函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。通过next()方法的参数,可以在generator函数运行后,继续向函数体内部注入值。也就是说,可以在generator函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。
function* fn(x){
let y=2*(yield (x+1));
let z=yield (y/3);
return x+y+z;
}
let f=fn(5);
console.log(f.next()) // {value: 6, done: false}
console.log(f.next()) // {value: NaN, done: false}
console.log(f.next()) // {value: NaN, done: true}
console.log(f.next()) // {value: undefined, done: true}
解析:
1、第一次调用next()方法,函数只会执行到yield (5+1)暂停,并返回{value: 6, done: false}
2、第二次调用next()方法,没有传递参数,所以y的值是undefined,那么y/3是一个NaN,所以返回{value: NaN, done: false}
3、第三次调用next()方法,没有传递参数,所以z的值是undefined,6+undefined+undefined=NaN,返回{value: NaN, done: true}
如果向next()方法提供参数,那么返回结果就完全不一样了:
function* fn(x){
let y = 2*(yield (x+1));
let z = yield (y/3);
return x+y+z;
}
let f=fn(5);
console.log(f.next()) // {value: 6, done: false}
console.log(f.next(9)) // {value: 6, done: false}
console.log(f.next(2)) // {value: 25, done: true}
console.log(f.next()) // {value: undefined, done: true}
解析:
1、第一次调用next()方法,小括号里为(5+1),由于小括号内被yield暂停,所以返回 {value: 6, done: false}
2、第二次调用next()方法,上一次是在小括号内暂停的,所以这次从小括号内开始,由于next()传入的值是上一个yield返回的值,所以上一个yield返回的6作废,这次返回的是9,成了let y = 2 * (9),y为18,所以第二次调用next()方法时,返回的是yield (y/3) 也就是18/3,结果为 {value: 6, done: false}
3、第三次调用next()方法,参数2被当做是上一个yield的返回值,也就是yield (y/3)的返回值,即z=2,所以x+y+z=5+18+2=25,返回 {value: 25, done: true}
与iterator接口的关系:
ES6规定,默认的iterator接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterator)。
Symbol.iterator属性本身是一个函数,就是当前数据结构默认的遍历器生成函数,执行这个函数,就会返回一个遍历器。
由于执行generator函数实际返回的是一个遍历器,因此可以把generator赋值给对象的Symbol.iterator属性,从而使得该对象具有iterator接口。
let obj={};
function* fn(){
yield 4;
yield 5;
yield 6;
}
obj[Symbol.iterator]=fn; // 将generator赋值给对象的Symbol.iterator属性,传统对象没有部署原生iterator接口,不能使用for...of循环和扩展运算符,现在通过给对象添加Symbol.iterator属性和对应的遍历器生成函数,就可以使用了
for(let value of obj){
console.log(value) // 4 5 6
} console.log([...obj]) // [4, 5, 6]
for...of循环:
由于generator函数运行时生成的是一个iterator对象,因此,可以直接使用for...of循环遍历,且此时无需再调用next()方法。
这里需要注意,一旦next()方法返回对象的done属性为true,for...of循环就会终止,且不包含该返回对象。
function* fn() {
yield 1;
yield 2;
yield 3;
yield 4;
return 5;
}
for(let item of fn()){
console.log(item) // 1 2 3 4 ----done为true了,for...of循环终止了,返回不了5
}
前面学过fon...in用来遍历对象,for...of用来遍历数组。为什么呢?
原生数组具备iterator接口,即默认部署了Symbol.iterator属性,给对象手动部署Symbol.iterator属性,也可以使用for...of循环。
ES6借鉴C++、java、C#、Python语言,引入了for...of循环,作为遍历所有数据结构的统一的方法。
一个数据结构只要部署了Symbol.iterator属性,就被视为具有iterator接口,就可以用for...of循环遍历它的成员。也就是说,for...of循环内部调用的是数据结构的Symbol.iterator方法。
for...of循环可以使用的范围包括数组、Set和Map结构、某些类似数字的对象(如argument对象、DOM NodeList对象)、generator对象,字符串。
Generator.prototype.return():
generator函数返回的遍历器对象,还有一个return方法,可以返回给定的值(若没有提供参数,则返回值的value属性为undefined),并终结遍历generator函数。
function* fn() {
yield 1;
yield 2;
yield 3;
yield 4;
return 5;
}
var f=fn();
console.log(f.next()) // {value: 1, done: false}
console.log(f.next()) // {value: 2, done: false}
console.log(f.next()) // {value: 3, done: false}
console.log(f.next()) // {value: 4, done: false}
console.log(f.next()) // {value: 5, done: true}
console.log(f.next()) // {value: undefined, done: true} // 使用return()方法后 function* fn() {
yield 1;
yield 2;
yield 3;
yield 4;
return 5;
}
var f=fn();
console.log(f.next()) // {value: 1, done: false}
console.log(f.next()) // {value: 2, done: false}
console.log(f.return("end")) // {value: "end", done: false} ----如果没有传值,则value值为undefined
console.log(f.next()) // {value: undefined, done: false}
console.log(f.next()) // {value: undefined, done: true}
console.log(f.next()) // {value: undefined, done: true}
generator函数应用举例:
应用一:某公司的年会上有一个抽奖活动,总共6个人可以抽6次,没抽一次,抽奖机会就会递减。
思路:按照常规做法就需要声明一个全局变量来保存剩余的可抽奖次数,而全局变量会造成全局污染,指不定什么时候就被重新赋值了,所以往往不会大家推荐。
let count=6;
function draw() {
// 执行一段抽奖的逻辑
console.log(`剩余${count}次`)
}
function startDrawing() {
if(count>0){
count--;
draw(count);
}
}
let btn=document.createElement("button");
btn.id="start";
btn.textContent="开始抽奖";
document.body.appendChild(btn); document.getElementById("start").addEventListener("click",function () {
startDrawing();
}); // addEventListener()的第三个参数默认是false,意思是冒泡阶段执行
事实上,抽奖通常是每个人自己来抽,每抽一次就调用一次抽奖方法,而不是点一次就一次性全部运行完,是可以暂停的,这个不就是generator函数的意义所在吗?
function draw(count) {
// 执行一段抽奖的逻辑
console.log(`剩余${count}次`)
}
function* remain(count) {
while (count>0){
count--;
yield draw(count);
}
}
let startDrawing=remain(6);
let btn=document.createElement("button");
btn.id="start";
btn.textContent="开始抽奖";
document.body.appendChild(btn); document.getElementById("start").addEventListener("click",function () {
startDrawing.next();
});
应用二:由于http协议是一种无状态协议,执行一次请求后服务器无法记住是从哪个客户端发起的请求,因此当需要实时把服务器数据更新到客户端时通常采用的方法是长轮询和websocket。这里也可以用generator函数来实现长轮询。
function* ajax() {
yield new Promise((resolve,reject)=>{
setTimeout(() => {
resolve({code:0});
}, 200);
});
}
// 长轮询的方法
function update(){
let promise=ajax().next().value;
promise.then(res=>{
if(res.code!=0){
setTimeout(() => {
console.log("2秒后继续查询……")
}, 2000);
}else{
console.log(res)
}
});
}
update();
编写一个产生斐波那契数列的函数:
var i=0;
function fib(n){
var t,
a=0,
b=1,
arr=[0,1];
while (arr.length<=n){
i++;
[a,b]=[b,a+b];
arr.push(b);
}
// for(let j=n;j>=arr.length;){
// i++;
// [a,b]=[b,a+b];
// arr.push(b)
// }
arr=arr.slice(1);
return arr;
}
console.log(fib(5))
console.log(i)
console.log(fib(10))
console.log(i)
generator(生成器)的更多相关文章
- ES6笔记(5)-- Generator生成器函数
系列文章 -- ES6笔记系列 接触过Ajax请求的会遇到过异步调用的问题,为了保证调用顺序的正确性,一般我们会在回调函数中调用,也有用到一些新的解决方案如Promise相关的技术. 在异步编程中,还 ...
- php中trait(性状)与generator(生成器)
PHP中trait(性状)与generator(生成器) 一.trait (性状) 最近在看Josh Lockhat的<Modern PHP>,这本书很薄.但是其中给出了一个很重要的学习方 ...
- ES6新特性:Javascript中Generator(生成器)
ES6的很多特性都跟Generator扯上关系,而且实际用处比较广, 包含了任何需要异步的模块, 比如ajax, filesystem, 或者数组对象遍历等都可以用到: Generator的使用: G ...
- generator生成器iterator遍历器和yield
generator方法()返回一个iterator 使用generator时永远先去调用generator()方法 for of对iterator的调用过程(babel参照) 1,_iterator. ...
- MyBatis Generator 生成器把其他数据库的同名表生成下来的问题
[问题] 使用MyBatis Generator生成器时,发现Mapper文件中出现字段与连接数据库不符,经过查找发现该表是其他数据库的同名表的字段. [解决问题] 在构造文件中,这里是generat ...
- ES6新特性三: Generator(生成器)函数详解
本文实例讲述了ES6新特性三: Generator(生成器)函数.分享给大家供大家参考,具体如下: 1. 简介 ① 理解:可以把它理解成一个函数的内部状态的遍历器,每调用一次,函数的内部状态发生一次改 ...
- Javascript中Generator(生成器)
阅读目录 Generator的使用: yield yield* next()方法 next()方法的参数 throw方法() return()方法: Generator中的this和他的原型 实际使用 ...
- Generator生成器函数
接触过Ajax请求的会遇到过异步调用的问题,为了保证调用顺序的正确性,一般我们会在回调函数中调用,也有用到一些新的解决方案如Promise相关的技术. 在异步编程中,还有一种常用的解决方案,它就是Ge ...
- Python高级语法之:一篇文章了解yield与Generator生成器
Python高级语法中,由一个yield关键词生成的generator生成器,是精髓中的精髓.它虽然比装饰器.魔法方法更难懂,但是它强大到我们难以想象的地步:小到简单的for loop循环,大到代替多 ...
- 取代Promise的Generator生成器函数
接触过Ajax请求的会遇到过异步调用的问题,为了保证调用顺序的正确性,一般我们会在回调函数中调用,也有用到一些新的解决方案如Promise相关的技术. 在异步编程中,还有一种常用的解决方案,它就是Ge ...
随机推荐
- MySQL中的存储过程、函数与触发器
一.对待存储过程和函数的态度 优点: 1.存储过程只在创建时进行编译,sql语句则每次执行都需要编译.能提高数据库执行速度. 2.简单复杂操作结合事物一起封装. 3.复用性高. 4.安全性高,可指定存 ...
- JavaScript forEach() 方法
JavaScript forEach() 方法 JavaScript Array 对象 实例 列出数组的每个元素: <button onclick="numbers.forEach( ...
- JavaScript对象及初识面向对象
一.对象 1.1对象是什么 对象是包含相关属性和方法的集合体 1.2什么是面向对象 面向对象仅仅是一个概念或者编程思想 通过一种叫做原型的方式来实现面向对象编程 二.创建对象 2.1自定义对象 2.1 ...
- python创建文件时去掉非法字符
1.函数作用 windows系统中文件名不能包含 \ / : * ? " < > |想要创建必须过滤掉这些字符 2.函数实现 import re def filename_fil ...
- paramiko 远程执行多个命令
转发博客如下 https://blog.csdn.net/c_base_jin/article/details/86561445
- HTML+css基础 css的几种形式
1.行间样式:将style写在标签内的充当标签标签属性 2.行内样式
- 【Zabbix】zabora批量部署
zabora简化批量部署 目的:简化部署zabora,批量监控数据库的常用指标 1 数据库用户赋权 上传cre_arp_monitor.sh,并且部署用户. [root@oradb ~]# chown ...
- GO学习笔记 - 模版渲染及多种输出
本文主题:基于内置的text/template实现Golang模版渲染,并将结果写入文件.屏幕.变量. 小慢哥的原创文章,欢迎转载 目录 ▪ 定义结构体 ▪ 定义模版文本 ▪ 模版渲染及输出方式 ▪ ...
- C#开发自动照片(图片)裁剪(缩放)工具
1.需求分析 用winform窗体程序,开发一个能够自动.批量对图片进行缩放和裁剪的程序. 原本想直接从网上找类型的工具直接用,但是无奈现在网上能找到的工具,要么不能用,要么就是很 恶心的下载完后还有 ...
- Python【day 10】函数进阶-动态函数
形参小结 1.位置参数2.默认值参数3.动态参数 1.*args 位置参数的动态传参. 系统会自动的把所有的位置参数聚合成元组 2.**kwargs 关键字参数的动态传参. 系统会自动的把所有的关键字 ...