【ES6】Generator函数详解

  • 一、Generator函数简介
    • 基本概念
    • 函数写法
    • yield关键字介绍
  • 二、next方法的参数
  • 三、for...of循环
  • 四、关于普通throw()与Generator的throw()
  • 五、Generator函数的应用【很重要】
    • 1、延迟函数
    • 2、简化函数的flag(Generator与状态机)
    • 3、异步操作的同步化表达
    • 4、函数的自动化控制【心生佩服】
  • 查看更多ES6教学文章:
  • 参考文献

引言:从Generator开始,才算是ES6相对高级的部分。之后的Promise、async都与异步编程有关。

一、Generator函数简介

先用最直白的话给大家介绍一下Generator函数:首先呢,Generator是一类函数,通过 * 号来定义。其次,Generator函数里特有的yield关键字,可以把函数里面的语句在执行时分步执行。用next()来执行。
    例如,定义一函数:
        function* test(){
            yield console.log(“1”);
            yield console.log(“2”);
            yield console.log(“3”);
        }
    var t=test();t.next();t.next();t.next();
    在执行第一个next的时候,输出1,第二个输出2,以此类推……这样,就把函数里面的内容分段执行了。
    下面请看详细介绍。

基本概念

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

/********        代码块1-1      ********/
function* helloWorldGenerator() {yield 'hello';yield 'world';return 'ending';
}
var hw = helloWorldGenerator();

代码块1-1定义了一个Generator函数helloWorldGenerator, 它内部有两个yield语句"hello"和“world",即该函数有3个状态: hello、 world 和return语句(结束执行)。
  Generator函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用Generator函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象。
  下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一条yield语句(或return语句)为止。换言之,Generator 函数是分段执行的,yield语句是暂停执行的标记,而next方法可以恢复执行。

/********        代码块1-2      ********/
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }

上面的代码块1-2共调用了4次next方法。运行解释如下:
  第1次调用,Generator 函数开始执行,直到遇到第一条yield语句为止。next方法返回一个对象,它的value属性就是当前yield语句的值hello,done属性的值false表示遍历还没有结束。
  第2次调用,Generator 函数从上次yield语句停下的地方,一直执行到下一条yield语句。
  第3次调用,Generator 函数从上次yield语句停下的地方,一直执行到return语句(如果没有return语句,就执行到函数结束)。next方法返回的对象的value属性就是紧跟在return语句后面的表达式的值(如果没有return语句,则value属性的值为undefined),done属性的值true表示遍历已经结束。
  第4次调用,此时Generator函数已经运行完毕,next方法返回的对象的value属性为undefined, done属性为true。以后再调用next方法,返回的都是这个值。
  总结:调用Generator函数,返回一个遍历器对象,代表Generator函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield语句后面那个表达式的值; done 属性是一个布尔值,表示是否遍历结束。

函数写法

ES6没有规定functon关键字与函数名之间的星号写在哪个位置。这导致下面代码块1-3的写法都能通过。

/********        代码块1-3      ********/
function * foo(x, y) { ... }
function *foo(x, y) { ... }
function* foo(x, y) { ... }
function*foo(x, y) { ... }

yield关键字介绍

由于Generator函数返回的遍历器对象只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield语句就是暂停标志。
  遍历器对象的next方法的运行逻辑如下。
  1、遇到yield语句就暂停执行后面的操作。并将紧跟在yield后的表达式的值作为返回的对象的value属性值。
  2、下一次调用next方法时再继续往下执行,直到遇到下条yield语句。
  3、如果没有再遇到新的yield语句,就一直运行到函数结束,直到returnr语句为止,并将return语句后面的表达式的值作为返回的对象的value属性值。
  4、如果该函数没有return语句,则返回的对象的value属性值为undefined。
  另外注意,yield语句不能用在普通函数中,否则会报错。

二、next方法的参数

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

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

代码块2-1中,第二次运行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。

三、for…of循环

for…of循环可以自动遍历Generator函数,且此时不再需要调用next方法。如代码块3-1

/********        代码块3-1      ********/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循环中。

四、关于普通throw()与Generator的throw()

Generator函数返回的遍历器对象都有一个throw方法,可以在函数体外抛出错误,然后在Generator函数体内捕获。
  我们知道在try...catch语句中,如果try语句中抛出了两个异常,当第一个异常抛出时,就会直接停止。

/********        代码块4-1      ********/
var g = function* () {while (true) {try {yield;} catch (e) {if (e != 'a') throw e;console.log('内部捕获', e);}}
};var i = g();
i.next();try {i.throw('a');i.throw('b');} catch (e) {console.log('外部捕获',e);}
//内部捕获a
//外部捕获b

但是,上面的代码块4-1中,遍历器对象i连续抛出两个错误。第一个错误被Generator函数体内的catch语句捕获,然后Generator函数执行完成,于是第二个错误被函数体外的catch语句捕获。
  注意,不要混淆遍历器对象的throw方法和全局的throw命令。上面的错误是用遍历器对象的throw方法抛出的,而不是用throw命令抛出的。后者只能被函数体外的catch语句捕获。

五、Generator函数的应用【很重要】

1、延迟函数

功能:对函数f()延迟2000ms后执行。见代码块5-1。

/********        代码块5-1      ********/
function * f(){console.log('执行了');
}var g = f();setTimeout(function () {g.next();
},2000);

2、简化函数的flag(Generator与状态机)

功能:把一些需要flag的函数,去掉flag,大大简化函数体。见代码块5-2与5-3。

/********        代码块5-2 原函数      ********/
var tickFlag = true;
var clock = function (){if(tickFlag)console.log('Tick');elseconsole.log('Tock');tickFlag=!tickFlag;
}
/********        代码块5-3 简化后函数        ********/
var clock = function* (){while(true){yield console.log('Tick');yield console.log('Tock');}
}

3、异步操作的同步化表达

功能:假如现在有两个api分别是加载页面和卸载页面。普通写法见代码块5-4,同步化表达见代码块与5-5。

/********        代码块5-4 原写法      ********///加载页面
showLoadingScreen();
//加载页面数据
loadUIDataAsynchronously();
//卸载页面
hideLoadingScreen();
/********        代码块5-5 同步化后写法       ********/
function* loadUI(){showLoadingScreen();yield loadUIDataAsynchronously();hideLoadingScreen();
}var load = loadUI();
//加载UI
load.next();
//卸载UI
load.next();

其实,类似代码块5-5的写法,Vue里面有个概念Bus(中央总线),还有Java里面的线程的总线,都极为相似。感兴趣可以去查一查。

4、函数的自动化控制【心生佩服】

功能:如果有一个多步操作非常耗时,采用回调函数可能会很复杂。这时利用Generator函数可以改善代码运行的流程,类似于自动化控制。见代码块5-6。

/********        代码块5-6 函数的自动化控制     ********/
function* longRunningTask() {try {var value1 = yield step1();var value2 = yield step2(value1);var value3 = yield step3(value2);var value4 = yield step4(value3);} catch (e) {// catch Error}
}scheduler(longRunningTask());//实现自动化控制function scheduler(task){setTimeout(function() {var taskObj = task.next(task.value);if(!taskObj.done){task.value = taskObj.value;}},0);
}

查看更多ES6教学文章:

1. 【ES6】let与const 详解
2. 【ES6】变量的解构赋值
3. 【ES6】字符串的拓展
4. 【ES6】正则表达式的拓展
5. 【ES6】数值的拓展
6. 【ES6】数组的拓展
7. 【ES6】函数的拓展
8. 【ES6】对象的拓展
9. 【ES6】JS第7种数据类型:Symbol
10. 【ES6】Proxy对象
11. 【ES6】JS的Set和Map数据结构
12. 【ES6】Generator函数详解
13. 【ES6】Promise对象详解
14. 【ES6】异步操作和async函数
15. 【ES6】JS类的用法class
16. 【ES6】Module模块详解
17. 【ES6】ES6编程规范 编程风格

参考文献

阮一峰 《ES6标准入门(第2版)》

【ES6】Generator函数详解相关推荐

  1. es6中的generator函数详解

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

  2. generator函数详解

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

  3. js中Generator函数详解

    文章目录 1. Generator的定义和执行 2. Generator中yield在赋值号左边的情况 3. Generator函数嵌套使用 4. 使用generator函数完成网络请求 1. Gen ...

  4. Javascript 函数详解

    Javascript 函数详解 1)函数声明: 通过关键字function定义,把函数作为变量来声明 函数声明后不会立即执行,会在我们需要的时候调用到. <script>function ...

  5. C++ - 随机数生成器(random-number generator) 的 详解 及 代码

    随机数生成器(random-number generator) 的 详解 及 代码 本文地址: http://blog.csdn.net/caroline_wendy/article/details/ ...

  6. C语言网络编程:accept函数详解

    文章目录 前言 函数描述 代码实例 如何得到客户端的IP 和 端口号 前言 当使用tcp服务器使用socket创建通信文件描述符,bind绑定了文件描述符,服务器ip和端口号,listen将服务器端的 ...

  7. 【FFmpeg】函数详解(三)

    FFmpeg函数详解 14.av_write_frame 15.av_interleaved_write_frame 16.av_write_trailer 17.avio_close 18.av_i ...

  8. 【FFmpeg】函数详解(二)

    FFmpeg函数详解 9.av_dump_format 10.avio_open 11.avformat_write_header 12.avcodec_send_frame 13.avcodec_r ...

  9. 【FFmpeg】函数详解(一)

    FFmpeg函数详解 一.错误码相关 1.AVERROR 2.av_strerror 3.其他错误码解释 二.编解码 1.获取编解码器 2.申请.释放上下文环境 3.打开编码器avcodec_open ...

最新文章

  1. python抓取微博数据中心_有哪些「神奇」的数据获取方式?
  2. vim 忽略大小写查找
  3. 【LeetCode】字符串 string(共112题)
  4. 4到20ma模拟量转换公式_西门子 S7-1200 模拟量转换
  5. 岗位推荐 | 微软小冰团队招聘数据挖掘/算法工程师实习生
  6. Spark _28_窗口函数
  7. Docker Compose搭建consul群集环境(了解Docker Compose及常用命令,Docker四种网络,Doker指定端口)
  8. r语言regexpr函数_R语言学习笔记-文本挖掘之字符处理(1)
  9. Go语言中的单例模式
  10. DOM基础、定时器、BOM基础
  11. Vue 组件间的传值(通讯)
  12. 2016蓝桥杯C++A:网友年龄
  13. 大数据实训:实验二-基本SQL练习
  14. envi插件大津法_IDL处理Himawari8-NC数据
  15. android re浏览器下载,re浏览器官方版下载_re浏览器app下载4.9.6 - 系统城
  16. opencv中 画六边形
  17. 手机厂商要和年轻人交朋友,性价比日渐式微?
  18. 职业作秀V1.7.9更新公告
  19. 网络视频的防盗与破解
  20. js+css如何制作(音频)图标【切换播放动画】效果?

热门文章

  1. php中的全局变量$_POST收集表单数据
  2. statTarget-基于QC样本的代谢组学数据校正
  3. java 按顺序取出json_java取json 的方法
  4. DNA提取方法对浮游生物群落研究结果的影响
  5. MPB:农科院田健、韩东飞等-​​水稻根系互作功能微生物的筛选方法
  6. Nature子刊:涵盖20多万个基因组的人体肠道微生物参考基因组集
  7. MITRE:利用微生物组时间序列数据推断与宿主状态变化相关的特征
  8. Python使用matplotlib可视化树状图、层次聚类系统树图、树状图根据给定的距离度量将相似点分组在一起、并根据点的相似性将它们组织成树状图链接起来(Dendrogram)
  9. pandas使用query函数基于判断条件获得dataframe中满足条件的数据行(row)的索引列表(index of rows matching conditions in dataframe)
  10. R语言ggplot2可视化堆叠的条形图(stacked bar plot)并在每一个条形图的的中间添加对应的数值、值标签定位在geom_col堆叠的条形图中的每个条形段的中间