ES6必知必会 (七)—— Generator 函数

Generator 函数

1.Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同,通常有两个特征:

  • function关键字与函数名之间有一个星号;

  • 函数体内部使用yield表达式,定义不同的内部状态
    //一个简单的 Generator 函数
    function *Generator(){
    yield 'Hello';
    yield 'World';

     return 'Hello World';
    }
    

2.Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,必须调用 next 方法,才能使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。实际上就是,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

function *Generator(){yield 'Hello';yield 'World'; return 'Hello World'; } let generator = Generator(); //无返回 generator.next(); //{"value":"Hello","done":false} generator.next(); //{"value":"World","done":false} generator.next(); //{"value":"Hello World","done":true} generator.next(); //{"value":undefined , "done":true} 

上述代码就是一个 Generator 函数的执行过程 :

  • 第一次调用 next() 方法, 遇到第一个 yield 表达式后返回一个对象,对象的 vlaue 属性值是第一个 yield 表达式的值 Hello , done属性是 false , 表示整个遍历还没有结束;
  • 第二次调用 next() 方法, Generator 函数从上次yield表达式停下的地方继续执行 , 遇到第二个 yield 表达式后返回一个对象,对象的 vlaue 属性值是第一个 yield 表达式的值 World , done属性是 false , 表示整个遍历还没有结束;
  • 第三次调用 next() 方法, Generator 函数从上次yield表达式停下的地方继续执行 ,一直执行到return语句(如果没有return语句,就执行到函数结束)。此时 next 方法返回的对象的value属性,就是 return语句后面的表达式的值(如果没有return语句,则value属性的值为undefined),done属性的值true,表示遍历已经结束;
  • 第四次调用,此时 Generator 函数已经运行完毕,next方法返回对象的value属性为undefined,done属性为true。以后再调用next方法,返回的都是这个值。

3.上述例子中我们可以得知,调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield表达式(或return)后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。

4.yield 表达式可以看做是Generator 函数的暂停标志,要注意的是 yield 表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行;

function* gen() { yield 123 + 456; } 

上面代码中,yield后面的表达式123 + 456,不会立即求值,只会在next方法将指针移到这一句时,才会求值(“惰性求值”);

5.yield 表达式与 return 语句都能返回紧跟在语句后面的那个表达式的值,不同的是遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而 return 语句不具备位置记忆的功能,而且一个函数里面,只能执行一个 return 语句,但是可以执行多个yield表达式,要注意的是 yield 表达式只能用在 Generator 函数里面,用在其他地方都会报错。

(function (){yield 1; })() // SyntaxError: Unexpected number 

6.Generator 函数可以不用yield表达式,这时就变成了一个单纯的暂缓执行函数

function *Generator() { console.log('Hello World!') } var generator = Generator(); setTimeout(function () { generator.next() }, 2000); // "Hello World" 

7.yield 表达式如果用在另一个表达式之中,必须放在圆括号里面,如果用作函数参数或放在赋值表达式的右边,可以不加括号

function *Generator() { console.log('Hello' + yield); // SyntaxError console.log('Hello' + yield 123); // SyntaxError console.log('Hello' + (yield)); // OK console.log('Hello' + (yield 123)); // OK } function *Generator() { foo( yield 'a' , yield 'b' ); // OK let input = yield; // OK } 

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

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

上述代码中,定义了一个可以无限运行的 Generator 函数,如果 next 方法没有参数,每次运行到 yield 表达式,变量 reset 的值总是 undefined ,当 next 方法带一个参数true时,变量reset就被重置为这个参数(即true),因此i会等于-1,下一轮循环就会从-1开始递增。

我们利用这个特性,在 Generator 函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为;

function *foo(x) { var y = 2 * (yield (x + 1)); var z = yield (y / 3); return (x + y + z); } var a = foo(5); a.next() // {value:6, done:false} a.next() // {value:NaN, done:false} a.next() // {value:NaN, done:true} var b = foo(5); b.next() // { value:6, done:false } b.next(12) // { value:8, done:false } b.next(13) // { value:42, done:true } 

上述代码 a 第二次运行 next 方法的时候不带参数,导致 y 的值等于2 * undefined(即NaN),除以3以后还是NaN,因此返回对象的value属性也等于NaN。第三次运行Next方法的时候不带参数,所以z等于undefined,返回对象的value属性等于5 + NaN + undefined,即NaN;

b 调用加了参数,结果就不一样了,第一次调用next方法时,返回 x+1 的值 6;第二次调用next方法,将上一次 yield 表达式的值设为 12 ,因此 y 等于 24,返回 y / 3 的值 8;第三次调用next方法,将上一次 yield 表达式的值设为 13 ,因此 z 等于 13 ,这时 x 等于 5,y 等于24,所以 return 语句的值等于 42;

ps:next 方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的

9.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 方法了,但是要注意的是,一旦 next 方法的返回对象的 done 属性为 true时,for...of循环就会中止,且不包含该返回对象,因此上面 return 语句返回的 6,不包括在for...of循环之中。

10.Generator函数返回的遍历器对象,还有一个return方法,可以返回给定的值,并且终结遍历Generator函数

function *Generator() { yield 1; yield 2; yield 3; } var g = Generator(); g.next() // { value: 1, done: false } g.return('ok') // { value: "ok", done: true } g.next() // { value: undefined, done: true } 

遍历器对象 g 调用 return 方法后,返回值的 value 属性就是 return 方法的参数 ok(如果 不提供参数,则返回值的 value 属性为 undefined) 。并且,Generator函数的遍历就终止了,返回值的 done 属性为 true,以后再调用 next 方法,value 总是返回 undefined , done 属性总是返回 true;

11.如果在 Generator 函数内部,调用另一个 Generator 函数,默认情况下是没有效果的。

function *Generator1() { yield 'a'; yield 'b'; } function *Generator2() { yield 'x'; Generator1(); yield 'y'; } for (let v of Generator2()){ console.log(v); } // "x" // "y" 

12.可以使用 yield* 表达式,用来在一个 Generator 函数里面执行另一个 Generator 函数来达到在 Generator 函数内部,调用另一个 Generator 函数的效果;

function *Generator1() { yield 'a'; yield 'b'; } function *Generator2() { yield 'x'; yield* Generator1(); yield 'y'; } // 等同于 function *Generator2() { yield 'x'; yield 'a'; yield 'b'; yield 'y'; } // 等同于 function *Generator2() { yield 'x'; for (let v of Generator2()) { yield v; } yield 'y'; } for (let v of Generator2()){ console.log(v); } // "x" // "a" // "b" // "y" 

13.Generator 可以暂停函数执行,返回任意表达式的值。这种特点使得 Generator 有多种应用场景;

  • 异步操作的同步化表达 , 可以利用 Generator 函数的暂停执行的效果,把异步操作写在 yield 表达式里面,等到调用next方法时再往后执行
    function *loading() {
    showLoadingScreen();
    yield loadDataAsync();
    hideLoadingScreen();
    }
    var loader = loading();
    // 加载loading
    loader.next()

     // 隐藏loadingloader.next()
    

上面代码中,第一次调用 loading 函数时,该函数不会执行,仅返回一个遍历器。下一次对该遍历器调用next方法,则会显示Loading界面(showLoadingScreen),并且异步加载数据(loadDataAsync)。等到数据加载完成,再一次使用next方法,则会隐藏Loading界面,整个逻辑就很清晰了~

  • 可以利用 Generator 函数用同步的方式部署 Ajax 操作

     function *main(url) { var result = yield request( url ); var resp = JSON.parse(result); console.log(resp.value); } function request(url) { makeAjaxCall(url, function(response){ it.next(response); }); } var ajax = main(); ajax.next(); 

上面代码的 main 函数,就是通过 Ajax 操作获取数据,要注意的是 makeAjaxCall 函数中的 next 方法,必须加上 response 参数,因为 yield 表达式,本身是没有值的,总是等于undefined;

  • 控制流管理,如果有一个多步操作非常耗时,采用回调函数,可能会写成下面这样:

     step1(function (value1) {step2(value1, function(value2) { step3(value2, function(value3) { step4(value3, function(value4) { // Do something with value4 }); }); }); }); 

如果采用 Promise 写法 ,

     Promise.resolve(step1).then(step2).then(step3).then(step4).then(function (value4) { // Do something with value4 }, function (error) { // Handle any error from step1 through step4 }) 

采用 Generator 函数来写 :

    function *longRunningTask(value1) { try { var value2 = yield step1(value1); var value3 = yield step2(value2); var value4 = yield step3(value3); var value5 = yield step4(value4); // Do something with value4 } catch (e) { // Handle any error from step1 through step4 } } 

然后使用一个自动化函数,按次序执行所有步骤

    scheduler(longRunningTask(initialValue));function scheduler(task) { var taskObj = task.next(task.value); // 如果Generator函数未结束,就继续调用 if (!taskObj.done) { task.value = taskObj.value scheduler(task); } } 

14.另外,Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行),遇到 yield 命令就暂停,等到执行权返回,再从暂停的地方继续往后执行,另外,它的函数体内外的数据交换和错误处理机制的特点使它可以作为异步编程的完整解决方案;




转载于:https://www.cnblogs.com/wntd/p/9013316.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/414080.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[vue] 在使用计算属性的时,函数名和data数据源中的数据可以同名吗?

[vue] 在使用计算属性的时,函数名和data数据源中的数据可以同名吗? 莫名其妙的问题。可以同名,但data会覆盖methods。并且本就不该同名,同名说明你命名不规范。然后解释为什么会覆盖,因为Props、methods、data、comput…

IDEA启动项目报错:Error:(1, 1) java: 非法字符: '\ufeff'

1. 报错信息 IDEA导入支付宝支付测试Demo启动报错,报错信息如下: Error:(1, 1) java: 非法字符: \ufeff Error:(1, 10) java: 需要class, interface或enum经测试,MyEclipse并没有报同样的错误信息。 2. 解决方法 在IDEA右下角将编码改为GBK&a…

[js] 请使用js实现一个秒表计时器的程序

[js] 请使用js实现一个秒表计时器的程序 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><meta http-equiv&quo…

IDEA中导入支付宝电脑网站支付测试Demo遇到的错误

前言 官方推荐Demo的运行环境为Eclipse&#xff0c;本次主要针对IDEA中导入遇到的一些问题 本地环境&#xff1a;IDEA Tomcat8.5 1、错误一 Error:(1, 1) java: 非法字符: \ufeff Error:(1, 10) java: 需要class, interface或enum请参考这篇文章&#xff1a;https://www.cnblo…

[js] 模拟 localStorage 时如何实现过期时间功能

[js] 模拟 localStorage 时如何实现过期时间功能 1.存储时记录下有效截止时间 2.取数据时判断是否超过有效时间,在有效期内则返回,不在则提示或返回空并且将其删除class MyStorage {get(key) {const wrapValue localStorage.getItem(key)if (wrapValue null) {return undefi…

表格

表格划分三部分&#xff1a;表头&#xff0c;主体&#xff0c;教主 thead&#xff1a;表格的头&#xff08;放标题之类的内容&#xff09; tbody&#xff1a;表格的主体&#xff08;放数据主体&#xff09; tfoot&#xff1a;表格的脚&#xff08;放表格的脚注&#xff09; 转载…

[js] 请使用js实现商品的自由组合,并说说你的思路

[js] 请使用js实现商品的自由组合&#xff0c;并说说你的思路 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><…

maven打包:找不到符号 符号: 方法 getXxx()

先交代一下我这边的情况&#xff0c;如若跟你想要的结果不符&#xff0c;勿喷。 聚合项目 使用过 mvn install 指令 聚合项目&#xff0c;主模块 A 需要打 war 包&#xff0c;其他模块 BCD 需要打为 jar 包作为其依赖。 先看一下报错截图&#xff1a; 这次的问题主要在于之前在…

DHCP的4步租约过程

请尊重原作者 &#xff1a;http://blog.51cto.com/yuanbin/109574 DHCP租约过程就是DHCP客户机动态获取IP地址的过程。 DHCP租约过程分为4步&#xff1a; ①客户机请求IP&#xff08;客户机发DHCPDISCOVER广播包&#xff09;&#xff1b; ②服务器响应&#xff08;服务器发DHCP…

[js] js中的undefined和 ReferenceError: xxx is not defined 有什么区别?

[js] js中的undefined和 ReferenceError: xxx is not defined 有什么区别&#xff1f; undefined是变量已声明&#xff0c;但未赋值 ReferenceError: xxx is not defined 是xxx未声明&#xff0c;使用了不存在的变量 个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端…

支付宝支付-支付宝PC端扫码支付

前言 支付宝支付—沙箱环境使用支付宝支付-支付宝PC端扫码支付「本文」支付宝支付-手机浏览器H5支付「待写」 PC端扫码支付&#xff0c;其实就是就是 电脑网站支付&#xff0c;本文基于支付宝沙箱环境&#xff0c;不了解的可以看一下上边的链接。 废话不多说&#xff0c;直接进…

[js]JavaScript Number.toPrecision() 函数详解

[js]JavaScript Number.toPrecision() 函数详解 JavaScript: numberObject.toPrecision( [ precision ] ) 如果没有提供precision参数或其值为undefined&#xff0c;则将转而调用toString()方法进行处理。 如果提供了参数&#xff0c;则参数precision必须介于 [1, 21] 之间&…

记一次sql优化之索引的引用

sql 优化时&#xff0c;一张200条数据的表&#xff0c;觉得数据量太少根本无须加索引&#xff0c;在一长段sql 优化无果后&#xff0c;发现这张表被频繁分组引用了四次&#xff0c;在外键加上索引后&#xff0c;成功优化。。。 所以数据量少的表&#xff0c;被频繁引用也需要加…

支付宝支付-手机浏览器H5支付

前言 支付宝支付—沙箱环境使用支付宝支付-支付宝PC端扫码支付支付宝支付-手机浏览器H5支付「本文」 手机浏览器支付&#xff0c;用户在安装支付宝APP的情况下&#xff0c;调用手机网站支付接口默认会唤起支付宝钱包支付&#xff0c;接下来通过运行官方Demo进行测试。 本文开发…

[js] 获取浏览器当前页面的滚动条高度的兼容写法

[js] 获取浏览器当前页面的滚动条高度的兼容写法 document.documentElement.scrollTop || document.body.scrollTop;个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#xff0c; 但坚持一定很酷。 主目录 与歌谣一起通关前端面试题

HTML5 input 类型

HTML5 Input 类型 HTML 4.01 与 HTML 5 之间的差异 以下类型是 HTML5 中的新类型&#xff1a;color, date, datetime, datetime-local, month, week, time, email, number, range, search, tel 以及 url。 浏览器支持 Input typeIEFirefoxOperaChromeSafariemailNo4.09.010.0No…

[js] 一道变态题 Number.call.call(Number, undefined, 0) 等于什么?

[js] 一道变态题 Number.call.call(Number, undefined, 0) 等于什么&#xff1f; call 的第一个参数用于改变上下文&#xff0c;由于没有用到 this&#xff0c;第一个参数 Number 实际上没有用。Number.call(Number, undefined, 0) 等价于 Number(undefined, 0)&#xff0c;由…

uniapp添加网站favicon文件

前言 uniapp 默认创建的项目并没有给我们提供加上网站 favicon 的 ”机会”&#xff0c;但其实官方已经给出解决方法了&#xff0c;使用的是 自定义模板 自定义模板的场景&#xff0c;通常有以下几种情况&#xff1a; 调整界面 head 中的 meta 配置补充 SEO 相关的一些配置「仅…

sed: -e expression #1, char 23: unknown option to `s'

语言&#xff1a;bash why? / 作为sed的分隔符&#xff0c;和需要操作的内容有冲突 way? 替换 / 分隔符为 # 或者其他分隔符 转载于:https://www.cnblogs.com/2bjiujiu/p/9029598.html

[js] ReferenceError和TypeError有什么区别?

[js] ReferenceError和TypeError有什么区别&#xff1f; ReferenceError 指的是引用出错&#xff0c;比如尝试访问未定义的变量&#xff0c;或者提前访问无提升的变量&#xff0c;都会引发这个错误&#xff1a; console.log(foo); // ReferenceError: foo is not defined l…