简述:ES6中Generator函数与yield关键字

ES6:Generator 函数 与 yield 关键字

一、Generator 函数 与 yield 引入

  1. 语法上:首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
    执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

  2. 形式上: Generator函数是一个普通函数,但是有两个特征:

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

示例:

function* count() {yield '1'; // yield表达式就是暂停标志,遇到 yield 表达式,就暂停执行后面的操作,并将紧跟在 yield 后面的那个表达式的值,作为返回的对象的 value 属性值。yield '2';yield '3';return '4';
}
var c = count(); // 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是:调用 `Generator` 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象(Iterator Object)**。我们需要一步步调用执行状态。// 简单理解: `Generator` 函数是分段执行的,内部 `yield` 表达式是**暂停执行的标记**,而 `next` 方法可以恢复执行。所以`Generator`函数运行如下:
// console.log(c) // [[GeneratorState]] : "suspended" //暂停、中止状态c.next() // 由于yieid表达式暂停到此,调用 next 方法时,恢复执行,往下执行遇到下一个 yield 表达式,继续暂停。
// { value: '1', done: false }
c.next()
// { value: '2', done: false }
c.next()
// { value: '3', done: false }
c.next()
// { value: '4', done: true } // 此时的Generator函数状态 [[GeneratorState]] : "closed" // 关闭,表示状态已经执行完毕
c.next()
// { value: undefined, done: true } // 状态已经完成,返回对象的value为undefined, 后续调用next()都是这个值
  1. Generator 函数可以不用 yield 表达式,这时就变成了一个单纯的暂缓执行函数。
	function* f() {console.log('执行了!')}var generator = f();setTimeout(function () {generator.next()}, 2000);
  1. yield表达式只能用在 Generator 函数里面
  2. yield 表达式如果用在另一个表达式之中,必须放在圆括号里面。
	function* demo() {console.log('Hello' + yield); // SyntaxErrorconsole.log('Hello' + yield 123); // SyntaxErrorconsole.log('Hello' + (yield)); // OKconsole.log('Hello' + (yield 123)); // OK}

二、next()方法传参

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

示例:

function* foo(x) {const y = yield x;const z = '这是从第3个next获取的' + (yield y); // y需要自定义,从哪里来? next()传入,传入的参数是上一个yield表达式返回的值yield z; // z同理return 5
}
var a = foo(5);
console.log(a.next())
console.log(a.next(1)) // 如果不传参数,则输出undefined
console.log(a.next(2))

再看一个示例:

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} 不带参数,导致 y 的值等于 2 * undefined (即 NaN ),除以 3 以后还是 NaN
a.next() // Object{value:NaN, done:true} 不带参数,所以 z 等于 undefined ,返回对象的 value 属性等于 5 + NaN + undefined ,即 NaN 。var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }

三、for...of遍历suspended状态的Generator 函数

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 方法的返回对象的 done 属性为 truefor...of 循环就会中止,且不包含该返回对象,所以上面代码的 return 语句返回的 6 ,不包括在 for...of 循环之中。

做个示例:

function* fibonacci() {let [prev, curr] = [0, 1];for (; ;) {yield curr;[prev, curr] = [curr, prev + curr];}
}
for (let n of fibonacci()) {if (n > 1000) break;console.log(n); // 1 1 2 3 5 8 .... 987
}

for (; ;) {}表示无限循环或无条件循环,必须设置break终止循环

	var count = 1for (; ;) {console.log(count)count++if (count > 5) {break}}

四、thorw()抛出错误

Generator函数返回的遍历器对象,都有一个 throw方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。

var g = function* () {try {yield;} catch (e) {console.log('内部捕获', e);}
};
var i = g();
i.next();
try {i.throw('a');i.throw('b');
} catch (e) {console.log('外部捕获', e);
}
// 内部捕获 a
// 外部捕获 b

上面代码中,遍历器对象 i 连续抛出两个错误。第一个错误被 Generator 函数体内的 catch 语句捕获。 i 第二次抛出错误,由于 Generator 函数内部的 catch 语句已经执行过了,不会再捕捉到这个错误了,所以这个错误就被抛出了 Generator 函数体,被函数体外的 catch 语句捕获。
示例2:

var g = function* () {try {yield 1;} catch (e) {console.log('内部捕获', e);}try {yield 2;} catch (e) {console.log('内部捕获1', e);}
};
var i = g();
i.next()
i.next() // 执行到此,已经进入第2个suspended的try..catch
try {i.throw('a');i.throw('a1');i.throw('b');
} catch (e) {console.log('外部捕获', e);
}
// 内部捕获1 b
// 外部捕获 a1

throw 方法可以接受一个参数,该参数会被 catch 语句接收,建议抛出 Error 对象的实例。

var g = function* () {try {yield;} catch (e) {console.log(e);}
};
var i = g();
i.next();
i.throw(new Error('出错了!'));
// Error: 出错了!(…)

五、return()

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

function* gen() {yield 1;yield 2;yield 3;
}
var g = gen();
g.next()        // { value: 1, done: false }
g.return(4) 	// { value: 4, done: true }  传入参数为终止时的返回值,如果不传则输出undefined
g.next()        // { value: undefined, done: true }// 等同于
function* gen() {yield 1;return 4yield 2;yield 3;
}

如果 Generator 函数内部有 try...finally 代码块,且正在执行 try 代码块,那么 return 方法会导致立刻进入 finally 代码块,执行完以后,整个函数才会结束。

function* numbers() {yield 1;try {yield 2;yield 3;} finally {yield 4;yield 5;}yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }

六、next()、throw()、return() 的共同点

next() 、throw() 、 return() 这三个方法本质上是同一件事,可以放在一起理解。它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换 yield 表达式。

  1. next() 是将 yield 表达式替换成一个值。
const g = function* (x, y) {let result = yield x + y;return result;
};
const gen = g(1, 2);
gen.next(); // Object {value: 3, done: false}
gen.next(1); // Object {value: 1, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = 1;
  1. throw() 是将 yield 表达式替换成一个 throw 语句。
gen.throw(new Error('出错了')); // Uncaught Error: 出错了
// 相当于将 let result = yield x + y
// 替换成 let result = throw(new Error('出错了'));
  1. return() 是将 yield 表达式替换成一个 return 语句。
gen.return(2); // Object {value: 2, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = return 2;

七、yield*表达式

  1. 看一个示例: 如果在 Generator 函数内部,调用另一个 Generator 函数。需要在前者的函数体内部,自己手动完成遍历。
function* foo() {yield 'a';yield 'b';
}
function* bar() {yield 'x';// 手动遍历 foo()for (let i of foo()) {console.log(i);}yield 'y';
}
for (let v of bar()){console.log(v);
}
// x
// a
// b
// y
  1. 上面代码中,foobar 都是 Generator 函数,在 bar 里面调用 foo ,就需要手动遍历 foo 。如果有多个 Generator 函数嵌套,写起来就非常麻烦。所以我们可以用yield*代替for...of
function* bar() {yield 'x';yield* foo();yield 'y';
}
// 等同于
function* bar() {yield 'x';yield 'a';yield 'b';yield 'y';
}
// 等同于
function* bar() {yield 'x';for (let v of foo()) {yield v;}yield 'y';
}
for (let v of bar()) {console.log(v);}

八、用法

  1. 封装异步请求
function* main(url, cb) {// showLoading();try {yield request(url, cb);} catch (e) {console.log(e)}// hideLoading();
}
function request(url, cb) {console.log(url)setTimeout(() => {// it.next()cb && cb('data')it.throw(new Error('报错了...'))}, 1000)
}var it = main("http://some.url", (data) => {console.log(data)
});
it.next();
  1. Generator 与状态机
    Generator 是实现状态机的最佳结构。比如,下面的clock函数就是一个状态机。
var ticking = true;
var clock = function() {if (ticking)console.log('Tick!');elseconsole.log('Tock!');ticking = !ticking;
}

上面代码的 clock 函数一共有两种状态( TickTock ),每运行一次,就改变一次状态。这个函数如果用 Generator 实现,就是下面这样。

var clock = function* () {while (true) {console.log('Tick!');yield;console.log('Tock!');yield;}
};

上面的 Generator 实现与 ES5 实现对比,可以看到少了用来保存状态的外部变量 ticking ,这样就更简洁,更安全(状态不会被非法篡改)、更符合函数式编程的思想,在写法上也更优雅。Generator 之所以可以不用外部变量保存状态,是因为它本身就包含了一个状态信息,即目前是否处于暂停态
详情请点击了解

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

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

相关文章

MySQL 命令总结篇-思维导图

一些常用命令以思维导图形式总结在这里了,掌握这些进行MySQL基本操作绝对没问题,加油!友友们可以根据这些思维导图进行知识总结。 目录 一、快速上手 二、SQL 语句分类(DDL、DML、DQL、DCL) 三、数据类型 四、约束…

探索AI去衣技术中的反射应用

在当今数字时代,人工智能(AI)技术的飞速发展已经渗透到了我们生活的方方面面。其中,图像处理和计算机视觉作为AI的重要分支,正不断推动着创新应用的边界。今天,我们要探讨的是一个颇具争议但又技术上颇为有…

MySQL相关知识点梳理

一、MySQL架构 连接层:处理连接,身份验证等。核心服务层:包含权限判断、查询缓存、解析器、查询优化器、执行引擎等。存储引擎层:负责管理数据的底层组件,定义了如何存储、索引和检索数据。数据存储层:与文…

[Algorithm][动态规划][子序列问题][最长递增子序列][摆动序列]详细讲解

目录 0.子序列 vs 子数组1.最长递增子序列1.题目链接2.算法原理详解3.代码实现 2.摆动序列1.题目链接2.题目链接3.代码实现 0.子序列 vs 子数组 子序列: 相对顺序是跟源字符串/数组是一致的但是元素和元素之间,在源字符串/数组中可以是不连续的一般时间…

金融行业数字化上云及信创改造过程中的一些问题及解决方案|合集①

Q:对金融机构来讲,什么是一切业务运行的前提? 金融机构的业务连续性对系统的稳定性要求极高。任何系统故障都可能导致严重的业务中断和经济损失。因此,金融机构需要IT基础架构能够提供高稳定性的服务,确保业务的连续运…

我的名字叫大数据

第1章 大家好,我叫大数据 1.1 我的家族传统:从我小小的祖先到壮大的我 1.1.1 最初的我:原始部落里的计数石头 大家好,我是你们人类文明的“老朋友”——大数据。你们知道吗?在我还没有变成你们手机、电脑里飞速跑动的那些数字前,我最初的模样可是一块块“计数石头”。…

Android Display Graphics #1 整体框架介绍一

软件基础 Android的framework层提供了一系列的图像渲染API,可绘制2D和3D。简单理解就是上层开发APP的小伙伴提供了接口,开发者可以直接显示对应的自己内容。但如果掌握了Display底层逻辑再写上层app,会有掌控力,出问题可以根据lo…

iOS自动连接已知Wi-Fi功能的实现

首先需要在配置文件申请的时候将hotspot勾选上&#xff0c;之后还要在x-code里添加对应的配置&#xff0c;由于我们并没有用到获取设备周边Wi-Fi的功能&#xff0c;所以就没申请相关权限 相关连接Wi-Fi代码如下&#xff1a; #import <NetworkExtension/NetworkExtension.h&…

利用ssh远程安装显卡驱动

问题描述&#xff1a; 远程另一台机器----一台有图形界面的pc机&#xff0c;以更新驱动。在系统的软件更新里&#xff0c;可选的驱动更新版本不满足要求。在终端里用ppa源更新驱动版本也没有可用的。&#xff08;看起来&#xff09;只能手动下载驱动更新。但这种方法中途需要关…

探索k8s集群的存储卷 emptyDir hostPath nfs

目录 一 含义 查看支持的存储卷类型 emptyDir存储卷 1.1 特点 1.2 用途 1.3部署 二、hostPath存储卷 一 含义 容器磁盘上的文件的生命周期是短暂的&#xff0c;这就使得在容器中运行重要应用时会出现一些问题。首先&#xff0c;当容器崩溃时&#xff0c;kubelet 会重…

全面掌握 Chrome DevTools:Web 开发者的终极攻略

全面掌握 Chrome DevTools:Web 开发者的终极攻略 Chrome DevTools 是一组内置于谷歌浏览器中的强大开发工具,旨在帮助开发者更高效地构建和调试 Web 应用。无论是前端开发、性能优化还是调试 JavaScript 代码,Chrome DevTools 都提供了丰富的功能和工具。本篇文章将详细介绍…

FreeRTOS【12】队列集使用

1.开发背景 基于以上的章节&#xff0c;了解了 FreeRTOS 多线程间的信号量、队列的使用&#xff0c;已经满足了日常使用场景。这个篇章要介绍的是队列集&#xff0c;实际上队列的升级版&#xff0c;存储信号量和队列等的触发事件。 队列集在实际的开发项目中应用相对比较少&…

chap4 simple neural network

全连接神经网络 问题描述 利用numpy和pytorch搭建全连接神经网络。使用numpy实现此练习需要自己手动求导&#xff0c;而pytorch具有自动求导机制。 我们首先先手动算一下反向传播的过程&#xff0c;使用的模型和初始化权重、偏差和训练用的输入和输出值如下&#xff1a; 我…

达梦数据库写文件的方式探索

0x01 前沿 这篇文章整体算是《达梦数据库手工注入笔记》的续集&#xff0c;达梦作为国内优秀的信创数据库&#xff0c;在关基单位中拥有越来越大的用户使用量。 通过SQL注入来写文件一直以来都是SQL注入漏洞深入利用的一种方式&#xff0c;对于不同的数据库通常写文件的方式也是…

刷代码随想录有感(86):贪心算法——跳跃游戏II(最小跳跃次数)

题干&#xff1a; 代码&#xff1a; class Solution { public:int jump(vector<int>& nums) {if(nums.size() 1)return 0;int curcover 0;int nextcover 0;int res 0;for(int i 0; i < curcover; i){nextcover max(i nums[i], nextcover);if(i curcover …

二叉树的链式存储

目录 1.二叉树的概念和性质2.二叉树的链式存储2.1二叉树的遍历2.1.1前中后遍历2.1.2层次遍历 2.2求节点的个数2.3求叶子节点的个数2.4求第k层节点个数2.5二叉树的销毁2.6怎样通过前序遍历构建二叉树2.7判断是否是满二叉树 1.二叉树的概念和性质 一&#xff0c;概念 1.五种形态…

掌握 JavaScript 基本输出方法

掌握 JavaScript 基本输出方法 前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&#xff1a;神的孩子都在歌唱 JavaScript 是一种强大且灵活的编程语言&#xff0c;广泛用于 Web 开发。通过 JavaScript&#xff…

NeuralForecast 推理 - 数据集从文件dataset.pkl读

NeuralForecast 推理 - 数据集从文件dataset.pkl读 flyfish from ray import tune from neuralforecast.core import NeuralForecast from neuralforecast.auto import AutoMLP from neuralforecast.models import NBEATS, NHITS import torch import torch.nn as nn import …

YOLOV8训练自己的数据集图文实战,包含voc数据集处理代码

yolov8官方链接: link 本文章是以labelimg标注好的voc数据集为基础,通过转换格式训练模型, 一,安装 pip install ultralyticsor pip install githttps://github.com/ultralytics/ultralytics.gitmainlink 二,数据集准备 数据集格式如下 ├── ultralytics └── datase…

RocketMq broker源码解析

broker 集群工作流程 NameSrv启动成功后&#xff0c;等待broker、Consumer和producer启动后也与NameSrv保持长连接, NameSrv相当于是路由控制中心。启动broker, broker与所有的NameSrv建立长连接, broker&#xff0c;通过定时线程定时向NameSrv发送心跳&#xff0c;broker信息…