es6必知必会——generator函数(代码片段)

流星飞雨 流星飞雨     2022-11-11     175

关键词:

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()

     // 隐藏loading
     loader.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 命令就暂停,等到执行权返回,再从暂停的地方继续往后执行,另外,它的函数体内外的数据交换和错误处理机制的特点使它可以作为异步编程的完整解决方案;




es6必知必会——class

1.在之前的JS面向对象编程中,如果定义一个构造函数,一般来说是这样:functionPerson(name,age)this.name=name;this.age=age;Person.prototype.say=function()return‘Mynameis‘+this.name+‘,Iam‘+this.age+‘yearsold‘;这种写法跟传统的面向对象语言(比如C++... 查看详情

scala必知必会(代码片段)

文章目录入门概述安装JavaVSScalaval和var基本数据类型lazy在Scala中的应用开发工具IDEAMaven函数方法定义默认参数命名参数可变参数条件语句循环语句面向对象概述类的定义和使用抽象类伴生类和伴生对象case和trait集合数组ListSetMapOpt... 查看详情

es6必知必会——字符串和函数的拓展

字符串的拓展1.ES6为字符串添加了遍历器接口,因此可以使用for...of循环遍历字符串2.字符串新增的includes()、startsWith()、endsWidth()三个方法用于判断某一字符串是否包含于另一字符串includes():返回布尔值,表示源字符串中是否包... 查看详情

es6必知必会——module

Module1.ES6在语言标准的层面上,实现了模块功能,成为浏览器和服务器通用的模块解决方案,完全可以取代CommonJS和AMD规范,基本特点如下:每一个模块只加载一次,每一个JS只执行一次,如果下次再去加载同目录下同文件,直接... 查看详情

es6必知必会——symbolset和map

Symbol1.Symbol是ES6引入了一种新的原始数据类型,表示独一无二的值。它是JavaScript语言的第七种数据类型,前六种分别是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object);2.Symbol值通过Symbol函数... 查看详情

es6必知必会——变量声明和结构赋值

本文章属于个人对es6一些比较常用的语法的总结归纳,其主要参考阮一峰大神的<ahref="http://es6.ruanyifeng.com//">ECMAScript6入门</a>,如有哪里理解不对或者不透彻的地方,还请批评指正~ps:推荐使用es6在线调试<b>let和const... 查看详情

mysql学习--mysql必知必会(代码片段)

?上图为数据库操作分类:??下面的操作參考(mysql必知必会)创建数据库运行脚本建表:mysql>createdatabasemytest;QueryOK,1rowaffected(0.07sec)mysql>showdatabases;+--------------------+|Database|+--------------------+|infor 查看详情

es6必知必会——promise对象

Promise对象1.Promise对象是ES6对异步编程的一种解决方案,它有以下两个特点:Promise对象代表一个异步操作,它只有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已失败),并且该状态不会受外界的影响Pr... 查看详情

carson带你学java:关于string类的必知必会!(代码片段)

概述关于String类的必知必会主要包括:String的常用函数equals()与==的区别String、StringBuffer与StringBuilder的区别Switch能否用string做参数?1.String常用函数2.equals()与==的区别附:/***附1:Object的equalsÿ 查看详情

hive必知必会(代码片段)

hive: 基于hadoop,数据仓库软件,用作OLAPOLAP:onlineanalyzeprocess 在线分析处理OLTP:onlinetransactionprocess在线事务处理 事务: ACID A:atomic 原子性 C:consistent 一致性 I:isolation 隔离性 D:durability 持久性 1读未提交   脏读 //事务... 查看详情

省时提效!5个必知必会的sql窗口函数!(代码片段)

SQL是数据世界中的通用语言,是作为数据科学人员必备技能。它之所以如此重要,是因为许多数据探索、数据操作、管道开发和仪表板创建都是通过SQL完成的。想要利用SQL高效处理数据任务,掌握一些窗口函数非常有... 查看详情

es6必知必会——数组和对象的拓展

数组的扩展1.拓展运算符(‘...‘),它相当于rest参数的逆运算,用于将一个数组转换为用逗号分隔的参数序列;console.log(...[1,2,3])//123console.log(1,...[2,3,4],5)//123452.如果扩展运算符后面是一个空数组,则不产生任何效果;[...[],1]//... 查看详情

mysql必知必会(代码片段)

姊妹篇——Hive必知必会(数据仓库):https://hiszm.blog.csdn.net/article/details/119907136文章目录第一章:数据库基础基本概念什么是SQL第二章:MySQL简介第三章:了解数据库和表第四章:检索数据SELECT语句第五章:... 查看详情

crypto必知必会(代码片段)

crypto必知必会最近参加了个ctf比赛,在i春秋,南邮方面刷了一些crypto密码学题目,从中也增长了不少知识,在此关于常见的密码学知识做个小总结!Base编码Base编码中用的比较多的是base64,首先就说一下Base64编码方式将字符串以... 查看详情

springmvc--必知必会(代码片段)

  SpringMVC基于模型--视图--控制器(Model-View-Controller,MVC)模式实现,属于SpringFrameWork的后续产品,已经融合在SpringWebFlow里面。它通过一套注解,让一个简单的Java类成为处理请求的控制器,而无需实现任何接口。同时它还支持... 查看详情

h5系列之history(必知必会)(代码片段)

H5系列之History(必知必会)目录概念兼容性属性方法H5方法概念理解HistoryApi的使用方式目的是为了解决哪些问题作用:ajax获取数据时,可以改变历史记录,从而可以使用浏览器的后退和前进。【】规范地址:http://www.w3.org/TR/html5... 查看详情

必知必会的设计原则——合成复用原则(代码片段)

 设计原则系列文章 必知必会的设计原则——单一职责原则必知必会的设计原则——开放封闭原则必知必会的设计原则——依赖倒置原则必知必会的设计原则——里氏替换原则必知必会的设计原则——接口隔离原则必知必... 查看详情

小白学python:这10个pandas函数属于必知必会!(代码片段)

Hello大家好,今天我将和大家一起学习竞赛中的各种基础知识点,从基础库使用到具体的比赛案例。今天我们将学习Pandas,一个非常强大的数据分析、数据清洗和读取的工具,也是在Kaggle竞赛中必备的库。在这篇... 查看详情