你一定要知道的iterator和generator
generator是一种特殊的iterator,generator可以替代iterator实现,使代码更为简洁
什么是iterator
iterator叫做迭代器,是用来帮助某个数据结构进行遍历的对象,这个对象需要符合迭代器协议(iterator protocol)。
迭代器协议要求实现next方法,next方法有如下要求
- 0或者1个函数入参
- 返回值需要包括两个属性,done 和 value。
当遍历完成时, done 为 true,value 值为 undefined。
迭代器实现原理
- 创建一个指针对象,指向当前数据结构的起始位置
- 第一次调用对象的next方法,指针自动指向数据结构的第一个成员
- 接下来不断调用next方法,指针一直往后移动,直到指向最后一个成员
- 每调用next方法返回一个包含value 和 done 属性的对象
以下对象就实现了迭代器
const names = ["kiki", "alice", "macus"];
let index = 0;
const namesIterator = {
next: () => {
if (index == names.length) {
return { value: undefined, done: true };
} else {
return { value: names[index++], done: false };
}
},
};
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())
当第四次调用next方法时,此时数据已经迭代完成,所以迭代器返回 done 为 true

可迭代对象
可迭代对象与迭代器对象不同。
- 迭代器对象需要符合迭代器协议(iterator protocol),并且返回next方法。
- 可迭代对象需要实现iterable protocol协议,即 @@iterator 方法,在代码中通过 Symbol.iterator 实现,这样当数据进行for...of遍历时,就用调用@@iterator方法。
我们知道,对象这种数据类型是不可以通过for...of对其遍历的

但如果我们对它实现了@@iterator方法之后,它就变成了可迭代对象
const obj = {
name: "alice",
age: 20,
hobby: "singing",
[Symbol.iterator]: function () {
let index = 0;
const keys = Object.keys(this);
return {
next: () => {
if (index == keys.length) {
return { value: undefined, done: true };
} else {
const key = keys[index];
index++;
return { value: this[key], done: false };
}
},
};
},
};
for (let item of obj) {
console.log(item);
}
const iterator = obj[Symbol.iterator]()
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
可迭代对象的实现中包括了迭代器对象。

原生可迭代对象
简单来说,可以通过for...of遍历的就是可迭代对象,原生可迭代对象包括:数组、字符串、arguments、set、map
const arr = ["kiki", "alice", "macus"];
for (let item of arr) {
console.log("数组", item);
}
const str = "hello";
for (let s of str) {
console.log("字符串", s);
}
function foo() {
for (let arg of arguments) {
console.log("arguments", arg);
}
}
foo(1, 2, 3, 4);
const mapEntries = [
["name", "alice"],
["age", "20"],
];
const map = new Map(mapEntries);
for (let m of map) {
console.log("map", m);
}
const set = new Set(arr);
for (let s of set) {
console.log("set", s);
}
以上都是原生可迭代对象

可迭代对象的用途
可迭代对象有以下用途
- for...of遍历
- 展开语法
- 解构赋值
- 创建其他类型的对象,如array和set
- Promise.all也可以执行可迭代对象
对象可以使用展开语法和解构赋值,但它并不是可迭代对象,而是es9中单独实现的属性
const iteratorObj = {
names: ["kiki", "alice", "macus"],
[Symbol.iterator]: function () {
let index = 0;
return {
next: () => {
if (index == this.names.length) {
return { value: undefined, done: true };
} else {
return { value: this.names[index++], done: false };
}
},
};
},
};
for (let item of iteratorObj) {
console.log('for..of遍历可迭代对象:',item);
}
const newArr = [...iteratorObj];
console.log('展开语法:',newArr);
const [name1, name2, name3] = iteratorObj;
console.log('解构赋值:',name1, name2, name3);
const set = new Set(iteratorObj);
const arr = Array.from(iteratorObj);
console.log('set:',set);
console.log('array:',arr)
Promise.all(iteratorObj).then((value) => {
console.log("promise.all:", value);
});
以上方法都是获取next方法中的value值

自定义类的迭代
想要类变成可迭代对象,在类方法中添加 Symbol.iterator 方法并实现就可以了
class Student {
constructor(name, age, hobbies) {
this.name = name;
this.age = age;
this.hobbies = hobbies;
}
push(hobby) {
this.hobbies.push(hobby);
}
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index === this.hobbies.length) {
return { done: true, value: undefined }
} else {
return { value: this.hobbies[index++], done: false };
}
},
};
}
}
const student = new Student('kiki', '16', ['singing'])
student.push('swimming')
student.push('tennis')
for(let item of student){
console.log(item)
}
此时可以通过for..of方法遍历类中的hobbies属性

什么是generator
generator叫做生成器,可以用来控制函数什么时候执行和暂停。
生成器函数也是函数,但是和普通函数之间存在如下区别
- 生成器函数之间需要添加一个*
- 执行需要使用一个变量来接收, 每使用一次 next()方法执行一段代码
- 通过 yield 关键字暂停函数,yield既可以传参, 又有返回值
- 返回值是一个生成器(generator),生成器是一种特殊的迭代器
以上代码实现了生成器函数
function* foo(){
console.log('开始执行')
yield
console.log('world')
yield
console.log('结束执行')
}
const generator = foo()
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
调用next方法时,返回值与迭代器一致,为包含 value 和 done 的对象,此时value为undefine,因为yield后没有加上返回值

yield传参和返回值
通过next方法可以将参数传递到生成器函数中,通过yield可以返回数据
function* foo(value1){
console.log('开始执行')
const result1 = yield value1
const result2 = yield result1
const result3 = yield result2
console.log('结束执行')
}
const generator = foo('hello')
console.log(generator.next('世界'))
console.log(generator.next('merry'))
console.log(generator.next('christmas'))
console.log(generator.next('done'))
可以看到第一个next获取的value值是通过生成器函数传递的,而不是第一个next方法执行时的参数,所以value值为"hello"而不是"世界"

generator的其他方法
- throw方法用于抛出异常(需要在生成器函数中捕获)
- return方法用于中断生成器函数的执行
function* createGenerator() {
console.log("开始执行");
try {
yield "hello";
console.log("hello");
} catch (error) {
yield error;
}
yield "world";
console.log("结束执行");
}
const generator = createGenerator();
console.log(generator.next());
console.log(generator.throw("throw"));
console.log(generator.return("return"));
使用return方法后,done变为true,value就变成了return函数中传递的值

生成器替代迭代器
生成器是一种特殊的迭代器,通过生成器可以在某些场景做一些替换,使代码更为简洁
// 迭代器实现next方法
function createArrayIterator(arr) {
let index = 0;
return {
next: function () {
if (index == arr.length) {
return { value: undefined, done: true };
} else {
return { value: arr[index++], done: false };
}
},
};
}
// generator遍历暂停函数
function* createArrayGenerator(arr) {
for(let item of arr){
yield item
}
}
// yiled 语法糖
function* createArraYield(arr) {
yield* arr
}
const arr = ['alice', 'kiki', 'macus']
const iterator = createArrayIterator(arr)
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
const generator = createArrayGenerator(arr)
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
const yiledGen = createArraYield(arr)
console.log(yiledGen.next())
console.log(yiledGen.next())
console.log(yiledGen.next())
console.log(yiledGen.next())
以上三种方式所实现的功能是一致的

类中使用生成器
类中遍历的方式由迭代器改为生成器
class Student {
constructor(name, age, hobbies) {
this.name = name;
this.age = age;
this.hobbies = hobbies;
}
push(hobby) {
this.hobbies.push(hobby);
}
*[Symbol.iterator]() {
yield* this.hobbies
}
}
const student = new Student('kiki', '16', ['singing'])
student.push('swimming')
student.push('tennis')
for(let item of student){
console.log(item)
}

以上就是iterator和generator的用法和联系,关于js高级,还有很多需要开发者掌握的地方,可以看看我写的其他博文,持续更新中~
你一定要知道的iterator和generator的更多相关文章
- 关于C#你应该知道的2000件事
原文 关于C#你应该知道的2000件事 下面列出了迄今为止你应该了解的关于C#博客的2000件事的所有帖子. 帖子总数= 1,219 大会 #11 -检查IL使用程序Ildasm.exe d #179 ...
- C#开发人员应该知道的13件事情
本文讲述了C#开发人员应该了解到的13件事情,希望对C#开发人员有所帮助. 1. 开发过程 开发过程是错误和缺陷开始的地方.使用工具可以帮助你在发布之后,解决掉一些问题. 编码标准 遵照编码标准可以编 ...
- 关于WPF你应该知道的2000件事
原文 关于WPF你应该知道的2000件事 以下列出了迄今为止为WPF博客所知的2,000件事所创建的所有帖子. 帖子总数= 1,201 动画 #7 - 基于属性的动画 #686 - 使用动画制作图像脉 ...
- 每个JavaScript开发人员应该知道的33个概念
每个JavaScript开发人员应该知道的33个概念 介绍 创建此存储库的目的是帮助开发人员在JavaScript中掌握他们的概念.这不是一项要求,而是未来研究的指南.它基于Stephen Curti ...
- 你需要知道的 14 个常用的 JavaScript 函数
1.确定任意对象的具体类型 众所周知,JavaScript 中有六种原始数据类型(Boolean.Number.String.Null.Undefined.Symbol)和一个对象数据类型.但是你知道 ...
- 程序员必须要知道的Hadoop的一些事实
程序员必须要知道的Hadoop的一些事实.现如今,Apache Hadoop已经无人不知无人不晓.当年雅虎搜索工程师Doug Cutting开发出这个用以创建分布式计算机环境的开源软...... 1: ...
- 【转载】在IT界取得成功应该知道的10件事
在IT界取得成功应该知道的10件事 2011-08-11 13:31:30 分类: 项目管理 导读:前面大多数文章都是Jack Wallen写的,这是他的新作,看来要成为NB程序员还要不停的自我总结 ...
- 理工科应该的知道的C/C++数学计算库(转)
理工科应该的知道的C/C++数学计算库(转) 作为理工科学生,想必有限元分析.数值计算.三维建模.信号处理.性能分析.仿真分析...这些或多或少与我们常用的软件息息相关,假如有一天你只需要这些大型软件 ...
- 你应该知道的10个奇特的 HTML5 单页网站
网页设计师努力寻找新的方式来展现内容.其中一个大的趋势是单页网站,现在被世界上的一些大的品牌广泛采用,使用它们来为用户提供一个快速,干净和简单的而且美丽的网站. 下面是10个令人惊叹的单页 H ...
- Git / 程序员需要知道的12个Git高级命令
众所周知,Git目前已经是分布式版本控制领域的翘楚,围绕着Git形成了完整的生态圈.学习Git,首先当然是学习Git的基本工作流.相比于SVN等传统版本控制系统来说,Git是专为分布式版本控制而生的强 ...
随机推荐
- Prism Sample 7 Modules Directory
这种方式用扫描目录的方式来增加模块,具备最大的灵活性 仍然在App.xaml.cs中增加了以下代码 protected override IModuleCatalog CreateModuleCata ...
- 2020-01-25:redis中,哨兵如何选举?
福哥答案2020-01-25: [答案1:](https://bbs.csdn.net/topics/398982967)redis-sentinel故障转移的流程:1.当多个sentinel发现并确 ...
- 【汇编】老师太hun
老师只是随手发实验项目卡,从未提过实验报告的事情 可是 他却要在 复习周 一下子 收6次 实验报告 也不发资料,不说每次的时间点,不讲实验 这人心中有 学生 吗? 上课发 上个班直播的录播 一节课就发 ...
- Kafka实时数据即席查询应用与实践
作者:vivo 互联网搜索团队- Deng Jie Kafka中的实时数据是以Topic的概念进行分类存储,而Topic的数据是有一定时效性的,比如保存24小时.36小时.48小时等.而在定位一些实时 ...
- PyInstaller 完美打包 Python 脚本,输出结构清晰、便于二次编辑的打包程序
引入问题 如果我要写一个 Python 项目,打包成 exe 运行(方便在没有 Python 的电脑上使用),我需要打包出的根目录结构美观,没有多余的.杂乱的依赖文件在那里碍眼,而且需要在发现 bug ...
- Dapr在Java中的实践 之 服务调用
服务调用 通过服务调用(Service-to-service Invocation),服务可以使用 gRPC 或 HTTP 这样的标准协议来发现并可靠地与其他服务通信. Dapr采用边车(Sideca ...
- 流量劫持 —— GZIP 页面零开销注入 JS
前言 HTTP 代理给页面注入 JS 是很常见的需求.由于上游服务器返回的页面可能是压缩状态的,因此需解压才能注入,同时为了节省流量,返回下游时还得再压缩.为了注入一小段代码,却将整个页面的流量解压再 ...
- 【问题解决】 网关代理Nginx 301暴露自身端口号
一般项目上常用Nginx做负载均衡和静态资源服务器,本案例中项目上使用Nginx作为静态资源服务器出现了很奇怪的现象,我们一起来看看. "诡异"的现象 部署架构如下图,Nginx作 ...
- AI 和 DevOps:实现高效软件交付的完美组合
AI 时代,DevOps 与 AI 共价结合.AI 由业务需求驱动,提高软件质量,而 DevOps 则从整体提升系统功能.DevOps 团队可以使用 AI 来进行测试.开发.监控.增强和系统发布.AI ...
- 4.10 x64dbg 反汇编功能的封装
LyScript 插件提供的反汇编系列函数虽然能够实现基本的反汇编功能,但在实际使用中,可能会遇到一些更为复杂的需求,此时就需要根据自身需要进行二次开发,以实现更加高级的功能.本章将继续深入探索反汇编 ...