Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >JavaScript中的Generator(生成器)

JavaScript中的Generator(生成器)

作者头像
刘亦枫
发布于 2020-03-19 09:35:22
发布于 2020-03-19 09:35:22
1.4K00
代码可运行
举报
运行总次数:0
代码可运行

文章目录
  • 1.为什么要引入Generator?
  • 2.基本用法
  • 3.yield
  • 4.yield*
  • 5.next()方法
  • 6.next()方法的参数
  • 7.throw方法()
  • 8.return()方法
  • 9.Generator中的this和他的原型
  • 10.实际应用
  • 注:

1.为什么要引入Generator?

众所周知,传统的JavaScript异步的实现是通过回调函数来实现的,但是这种方式有两个明显的缺陷:

1.缺乏可信任性。例如我们发起ajax请求的时候是把回调函数交给第三方进行处理,期待它能执行我们的回调函数,实现正确的功能 2.缺乏顺序性。众多回调函数嵌套使用,执行的顺序不符合我们大脑常规的思维逻辑,回调逻辑嵌套比较深的话调试代码时可能会难以定位。

Promise恢复了异步回调的可信任性,而Generator正是以一种看似顺序、同步的方式实现了异步控制流程,增强了代码可读性。

generator是ES6提供的一种异步编程解决方案,在语法上,可以把它理解为一个状态机,内部封装了多种状态。执行generator,会生成返回一个遍历器对象。返回的遍历器对象,可以依次遍历generator函数的每一个状态。同时ES6规定这个遍历器是Generator函数的实例,也继承了Genarator函数的prototype对象上的方法。

Generator 函数是一个普通函数,但是有两个特征。

一是,function关键字与函数名之间有一个星号; 二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。

2.基本用法

Generator(生成器)是一类特殊的函数,跟普通函数声明时的区别是加了一个*号。

Iterator(迭代器):当我们实例化一个生成器函数之后,这个实例就是一个迭代器。可以通过next()方法去启动生成器以及控制生成器的是否往下执行。

yield/next:这是控制代码执行顺序的一对好基友。通过yield语句可以在生成器函数内部暂停代码的执行使其挂起,此时生成器函数仍然是运行并且是活跃的,其内部资源都会保留下来,只不过是处在暂停状态。在迭代器上调用next()方法可以使代码从暂停的位置开始继续往下执行。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();

hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }

上面的程序执行了四次next:

第一次调用,Generator 函数开始执行,直到遇到第一个yield表达式为止。next方法返回一个对象,它的value属性就是当前yield表达式的值hello,done属性的值false,表示遍历还没有结束。

第二次调用,Generator 函数从上次yield表达式停下的地方,一直执行到下一个yield表达式。next方法返回的对象的value属性就是当前yield表达式的值world,done属性的值false,表示遍历还没有结束。

第三次调用,Generator 函数从上次yield表达式停下的地方,一直执行到return语句(如果没有return语句,就执行到函数结束)。next方法返回的对象的value属性,就是紧跟在return语句后面的表达式的值(如果没有return语句,则value属性的值为undefined),done属性的值true,表示遍历已经结束。

第四次调用,此时 Generator 函数已经运行完毕,next方法返回对象的value属性为undefined,done属性为true。以后再调用next方法,返回的都是这个值。

3.yield

由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。

(1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。

(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。

(3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。

(4)如果该函数没有return语句或者执行完return之后再运行next的时候,则返回的对象的value属性值为undefined,done为true。

注意: 需要注意,yield表达式只能用在 Generator 函数里面,用在其他地方都会报错。 yield表达式如果用在另一个表达式之中,必须放在圆括号里面 yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号。 yield放在表达式中的时候,let s =(yield 1+2),s其值将会是undefined,而1+2这个等于3的值将会作为next返回对象的value的值

Generator函数返回的Iterator运行的过程中,如果碰到了yield, 就会把yield后面的值返回, 此时函数相当于停止了, 下次再执行next()方法的时候, 函数又会从上次退出去的地方重新开始执行;

如果把yield和return一起使用的话, 那么return的值也会作为最后的返回值, 如果return语句后面还有yield, 那么这些yield不生效:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function* gen() {
    yield 0;
    yield 1;
    return 2;
    yield 3;
};
let g = gen();
console.log(g.next(),g.next(),g.next(),g.next());
//输出:{ value: 0, done: false } { value: 1, done: false } { value: 2, done: true } { value: undefined, done: true }

4.yield*

yield这种语句让我们可以在Generator函数里面再套一个Generator, 当然你要在一个Generator里面调用另外的Generator需要使用: yield 函数() 这种语法, 都是套路啊:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function* foo() {
    yield 0;
    yield 1;
}
function* bar() {
    yield 'x';
    yield* foo();
    yield 'y';
}
for (let v of bar()){
    console.log(v);
};
//依次输出x,0,1,y

5.next()方法

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

这个参数就是解决了上面说的注意事项的最后一个,yield的返回值总是undefined,

由于next方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的。V8 引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。

Generator函数返回的Iterator执行next()方法以后, 返回值的结构为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
    value : "value", //value为返回的值
    done : false //done的值为一个布尔值, 如果Interator未遍历完毕, 他会返回false, 否则返回true;
}

所以我们可以模拟一个Generator生成器, 利用闭包保存变量, 每一次执行next()方法, 都模拟生成一个{value:value,done:false}的键值对:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function gen(array){
    var nextIndex = 0;
    return {
        next: function(){
            return nextIndex < array.length ?
            {value: array[nextIndex++], done: false} :
            {value: undefined, done: true};
        }
    };
};

var it = gen(["arr0", "arr1", "arr2", "arr3"]);
console.log( it.next() );
console.log( it.next() );
console.log( it.next() );
console.log( it.next() );
console.log( it.next() ); 

6.next()方法的参数

如果给next方法传参数, 那么这个参数将会作为上一次yield语句的返回值 ,这个特性在异步处理中是非常重要的, 因为在执行异步代码以后, 有时候需要上一个异步的结果, 作为下次异步的参数, 如此循环::

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<script>
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: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 }
</script>

7.throw方法()

如果执行Generator生成器的throw()方法, 如果在Iterator执行到的yield语句写在try{}语句块中, 那么这个错误会被内部的try{}catch(){}捕获 :

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<script>
var g = function* () {
    try {
        yield;
    } catch (e) {
        console.log('内部捕获0', e);
    }
};

var i = g();
i.next(); //让代码执行到yield处;
try {
    i.throw('a');
} catch (e) {
    console.log('外部捕获', e);
}
</script>

8.return()方法

如果执行Iterator的return()方法, 那么这个迭代器的返回会被强制设置为迭代完毕, 执行return()方法的参数就是这个Iterator的返回值,此时done的状态也为true:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<script>
function* gen() {
    yield 0;
    yield 1;
    yield 2;
    yield 3;
};
let g = gen();
console.log(g.return("heheda")); //输出:{ value: 'heheda', done: true }
</script>

9.Generator中的this和他的原型

Generator中的this就是谁调用它,那么this就是谁, 我们利用Reflect.apply可以改变Generator的上下文:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function* gen() {
    console.log(this);
    yield 0;
};
console.log(gen().next());
console.log(Reflect.apply(gen,"heheda").next());

10.实际应用

1.比如抽奖环节,当前用户还可以抽奖5次。点击后次数减1。

若采用ES5的方式,不使用Generator,则需要将count存入全局变量中,但是这样非常不安全,如果别人知道变量是什么,就可以修改变量;另外存入全局变量也会影响性能。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
    let draw=function(count){
        //具体抽奖逻辑,跟次数的校验是分开的
        //输出剩余次数
        console.log(`剩余${count}`)
    }
   //利用Generator控制次数
    let residue=function*(count){
        while(count>0){
            count--
            yield draw(count)
        }
    }
  //将Generator实例化,通过按钮绑定,点击执行next,进行抽奖
    let star=residue(5)
    let btn=document.createElement('button')
    btn.id='start'
    btn.textContent='抽奖'
    document.body.appendChild(btn)
    document.getElementById('start').addEventListener('click',function(){
        star.next()
    },false)
}

2.长轮询

场景:服务端的某一个数据状态定期变化,前端需要定时的去服务端取这个状态

对于这种场景,有两种解决方案

1)长轮询(定时器,定时访问接口)

2)websocket(浏览器兼容性不好)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
	let ajax=function* (){
		yield new Promise(function(resolve,reject){
			setTimeout(function(){
				resolve({code:0})
			},200)
		})
	}
 
	let pull=function(){
		let generator=ajax()
		let step=generator.next()
		step.value.then(function(d){
			if(d.code!=0){
				setTimeout(function(){
					console.log('wait')
					pull()
				},1000)
			}else{
				console.log(d)
			}
		})
	}
 
	pull()
}

输出结果为

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{code: 0}

将resolve({code:0})中code改成1,会一直轮询,输出结果为

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
wait
wait
wait
...

注:

ES6 诞生以前,异步编程的方法,大概有下面四种。

1.回调函数。 2.事件监听。 3.发布/订阅。 4.Promise 对象。

Generator 函数将 JavaScript 异步编程带入了一个全新的阶段。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
深入理解 ES6 Generator
而使用 ES6 Generator 可以将执行的循环停下,步骤如下: 1、在 loop 前面加一个星号 2、在输出前面加 yield 3、定义一个变量将 loop 赋值给 l
Leophen
2021/06/22
3070
【深扒】深入理解 JavaScript 中的生成器
在上篇文章中,我们深入了理解了迭代器的原理和作用,这一篇我们来深扒与迭代器息息相关的生成器。
小丞同学
2021/08/16
3370
理解 ES6 generator
与 promise 对象类似,这里运用鸭子模型进行判断,如果对象中有 next 与 throw 两个方法,那么就认为这个对象是一个生成器对象。
翼德张
2022/01/13
2480
Generator函数
JavaScript是单线程的,异步编程对于 JavaScript语言非常重要。如果没有异步编程,根本没法用,得卡死不可。
木子星兮
2020/07/16
1.1K0
ES6-标准入门·Generator 函数
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。此前,只在 dva(内部封装 redux-saga)里使用过,此次深入了解之。
数媒派
2022/12/01
3870
ES6的迭代器(Iterator)和生成器(Generator)
ES6引入了迭代器和生成器的概念,这两个特性为JavaScript带来了更强大的迭代和异步编程能力。本文将深入探讨ES6的迭代器和生成器,介绍它们的概念、用法以及在实际开发中的应用。
can4hou6joeng4
2023/11/17
4820
JavaScript 异步编程指南 — 了解下 Generator 更好的掌握异步编程
Generator 是 ES6 对协程的实现,提供了一种异步编程的解决方案,和 Promise 一样都是线性的模式,相比 Promise 在复杂的业务场景下避免了 .then().then() 这样的代码冗余。
五月君
2021/07/15
6590
从零开始学 Web 之 ES6(五)ES6基础语法三
在这里我会从 Web 前端零基础开始,一步步学习 Web 相关的知识点,期间也会分享一些好玩的项目。现在就让我们一起进入 Web 前端学习的冒险之旅吧!
Daotin
2018/09/30
4570
从零开始学 Web 之 ES6(五)ES6基础语法三
ES6: 迭代器与生成器
迭代器是一个对象,它拥有一个next方法,调用next方法会返回一个对象,该对象有两个属性值,value和done。每次调用next方法,返回的value表示可迭代对象中的下一个值,done表示迭代是否完成。根据此定义,可用下面的代码实现一个迭代器:
宅蓝三木
2024/10/09
1110
javascript迭代器和生成器(一)
在看过官方文档和《你不知道的javascript 中卷》之后,觉得还是应该写点什么总结一下,但是这个涉及到的点很多,感觉不太能够在单篇文章的篇幅之内能描述完全。
砖业洋__
2023/05/06
1950
javascript迭代器和生成器(一)
ES6之Generator
生成器是一种返回迭代的函数,(其实生成器函数也就是返回了一个iterator对象)。通过function关键字后边的星号(*)来表示,函数中存在新的关键字yield。星号可以紧挨着function关键字,也可以在中间添加一个空格。
19组清风
2021/11/15
2760
Generator函数
生成器generator是ES6标准引入的新的数据类型,一个generator看上去像一个函数,但可以返回多次,通过yield关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案。
WindRunnerMax
2020/08/27
4410
Iterator与Generator
Iterator 提供了一种统一的接口机制,为各种不同数据结构提供统一的访问机制。定义 Iterator 就是提供一个具有 next() 方法的对象,每次调用 next() 都会返回一个结果对象,该结果对象有两个属性,value 表示当前的值,done 表示遍历是否结束。
闻说社
2022/08/19
4320
Node理论笔记:异步编程
在JavaScript中,函数是一等公民,使用非常自由,无论是调用它,或者作为参数,或者作为返回值均可。
Ashen
2020/06/01
1.1K0
JavaScript之生成器
生成器是一个函数的形式,通过在函数名称前加一个星号(*)就表示它是一个生成器。所以只要是可以定义函数的地方,就可以定义生成器
赤蓝紫
2023/03/11
4010
JavaScript之生成器
Iterator 、Generator(一)
调用Generator函数,返回一个遍历器对象,代表Generator函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着value和done表达式的值;done属性是布尔值,表示是否遍历结束。
用户3258338
2019/08/15
4730
es6 --- Generator 函数
在 ES6 出现之前,基本都是各式各样类似Promise的解决方案来处理异步操作的代码逻辑,但是 ES6 的Generator却给异步操作又提供了新的思路,马上就有人给出了如何用Generator来更加优雅的处理异步操作。
小蔚
2019/09/11
6760
generator生成器
generator(生成器)是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次。
OECOM
2020/07/01
8580
async/await 原理及执行顺序分析
之前写了篇文章《这一次,彻底理解Promise原理》,剖析了Promise的相关原理。我们都知道,Promise解决了回调地狱的问题,但是如果遇到复杂的业务,代码里面会包含大量的 then 函数,使得代码依然不是太容易阅读。
桃翁
2019/11/08
2K0
【翻译】ES6生成器简介
原文地址:http://davidwalsh.name/es6-generators ES6生成器全部文章: The Basics Of ES6 Generators Diving Deeper With ES6 Generators Going Async With ES6 Generators Getting Concurrent With ES6 Generators Generator function是ES6带来的新功能之一。这个名字看起来很怪异,然而它的功能在接触之初看起来更加怪异。这篇文章的目
寒月十八
2018/01/30
8410
相关推荐
深入理解 ES6 Generator
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验