首页 > 其他分享 >【ES6】Generator函数详解

【ES6】Generator函数详解

时间:2023-01-16 17:33:10浏览次数:37  
标签:语句 ES6 函数 Generator yield next 详解


【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 属性是一个布尔值,表示是否遍历结束。


函数写法


代码块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循环

代码块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


一旦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');
else
console.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,yield,next,详解
From: https://blog.51cto.com/u_15942590/6010586

相关文章

  • 【ES6】Promise对象详解
    【ES6】Promise对象详解​​一、Promise对象的含义​​​​二、Promise对象的用法​​​​三、Promise对象的几个应用【重点】​​​​1、时间延迟函数​​​​2、图片异步......
  • 【ES6】JS的Set和Map数据结构
    【ES6】JS的Set和Map数据结构​​一、Set​​​​1、基本用法​​​​2、4种操作方法​​​​3、4种遍历方法​​​​4、Set的应用​​​​1)Set转化为数组​​​​2)去除数组......
  • 【ES6】Proxy对象
    【ES6】Proxy对象​​一、Proxy的基本用法​​​​二、Proxy示例的方法​​​​1)get()​​​​2)set()​​​​3)apply()​​​​查看更多ES6教学文章:​​​​参考文献​​引......
  • 【ES6】JS第7种数据类型:Symbol
    【ES6】JS第7种数据类型:Symbol​​一、Symbol的由来​​​​二、Symbol的涵义​​​​三、Symbol的判等​​​​四、Symbol的运算与转化​​​​查看更多ES6教学文章:​​​......
  • 【ES6】对象的拓展
    【ES6】对象的拓展​​一、对象的两种表示法【掌握】​​​​1)简洁表示法​​​​2)属性名表达式法​​​​二、Object.is()【了解】​​​​三、Object.assign()【了解】​......
  • Log4Net组件的应用详解
     第一步:添加并应用Log4net.dll。然后在Web.config文件中添加下面的配置局<configSections><sectionname="log4net"type="log4net.Config.Log4NetConfigurationSectionH......
  • GShang博客园主题高级版本适配教程详解
    主题预览博客Favicon自定义导航栏扩展博客内部公告信息首页轮播信息侧边栏公告信息整合博文发布信息位置调整博文侧边目录博文代码块复制博文作者信息签名博客捷径应用(更多......
  • ChatGPT/InstructGPT详解
    作者:京东零售刘岩​前言GPT系列是OpenAI的一系列预训练文章,GPT的全称是GenerativePre-TrainedTransformer,顾名思义,GPT的目的就是通过Transformer为基础模型,使用预训练技......
  • 神经网络基础部件-损失函数详解
    本文总结分类和回归任务的常用损失函数,比如重点解析了交叉熵损失函数的由来,并给出详细计算公式和、案例分析、代码,同时也描述了MAE和MSE损失函数,给出了详细的计算公式......
  • logging 模块详解
    日志记录函数以它们用来跟踪的事件的级别或严重性命名。下面描述了标准级别及其适用性(从高到低的顺序)日志等级(level)描述DEBUG 最详细的日志信息,典型应用场景是问......