JavaScript红宝书第七章:迭代器与生成器

news/2024/10/17 6:31:00/

JavaScript红宝书第七章:迭代器与生成器

  • 理解迭代
  • 迭代器模式
    • 可迭代对象
    • 可迭代协议
      • 什么是工厂函数?
      • 实现Iterator接口的内置类型
      • 什么是arguments?
      • 如何检查是否有迭代接口以及工厂函数
    • 迭代器协议
      • next方法
      • 自定义迭代器
    • 提前终止迭代器
  • 生成器
    • 定义
      • yield可以干嘛
        • 生成器对象可以作为可迭代对象
        • 使用 yield 实现输入和输出
        • 产生可迭代对象
        • 使用 yield*实现递归算法
    • 提前终止生成器
      • return方法
      • throw方法

理解迭代

什么是迭代?迭代就是有条件的循环,按顺序访问可迭代结构中的每一项。如下图:

for(let i=0;i<5;i++){console.log(i)
}

实现了一个最简单的迭代计数循环。但如果说我们想用一个迭代方法去迭代多种不同数据类型的内容,ES5新增了ForEach方法往通用迭代迈进了但仍不理想。

let arr=[1,2,3,4]
arr.forEach(item=>console.log(item))//1,2,3,4

它解决了提取数组索引,和数组值问题,但是不能标识迭代数组的终止条件。所以ES6新增了一个迭代器模式,解决了通用迭代功能。

迭代器模式

迭代器模式是一个方案,它把有些结构称为可迭代对象,而这些对象都实现了Iterable接口,可以通过Iterator迭代器消费。而iterator是按需创建的一次性对象。iterrator迭代器会暴露关联的可迭代对象的迭代API,实现无需知道可迭代对象结构就能实现迭代。

可迭代对象

具有有限元素且无歧义遍历顺序的对象叫可迭代对象。

可迭代协议

实现迭代API(可迭代协议)需要具备两个能力:

  • 支持迭代的自我识别能力
  • 创建实现IteratorAPI的对象能力
    所以说,每个可迭代对象都有一个默认迭代器属性,这个属性名必须是Symbol.Iterator且这个属性必须引用一个迭代器工厂函数,调用工厂函数必须返回一个新迭代器。

什么是工厂函数?

工厂函数类似于自动化机器,往这个函数里面投放它需要的参数就可以得到想要的产品。

实现Iterator接口的内置类型

  • 字符串
  • 数组
  • 映射
  • 集合
  • arguments对象
  • NodeList等Dom集合类型

什么是arguments?

可以移步我简述arguments的博客:原生JavaScript之函数特殊对象arguments

如何检查是否有迭代接口以及工厂函数

通过检查是否有[Symbol.Iterator]默认属性,来判断是否存在工厂函数。

let arr=[1,2,3,4]let str='123'let num=123console.log(arr[Symbol.iterator]);//values() { [native code] }console.log(str[Symbol.iterator]);// [Symbol.iterator]() { [native code] }console.log(num[Symbol.iterator]);// undefined

而通过在这个默认属性加()即可调用迭代工厂函数。

let arr=[1,2,3,4]
let str='123'
console.log(arr[Symbol.iterator]());// ay Iterator {}
console.log(str[Symbol.iterator]());// ingIterator {}

实现写代码过程中,我们不需要通过调用工厂函数生成迭代器,可以直接调用可迭代对象通用的方法特性来实现迭代:

  • for of
  • 数组解构
  • 扩展操作符
  • Array.from()
  • 创建集合
  • 创建映射
  • Promise.all
  • Promise.race
  • yield *(生成器)

实例:

let arr=[1,2,3,4]
// for of
for(item of arr){console.log(item);//1 2 3 4
}
// 数组解构
let [a,b,c]=arr
console.log(a,b,c);//1 2 3
// 扩展操作符
let arr2=[...arr]
console.log(arr2);//[1,2,3,4]
// Array.from
let arr3=Array.from(arr)
console.log(arr3);//[1,2,3,4]
// Set 构造函数
let set = new Set(arr); 
console.log(set); // Set(4) {'1', '2', '3',4} 
// Map 构造函数
let pairs = arr.map((x, i) => [x, i]); 
console.log(pairs); // [['1', 0], ['2', 1], ['3', 2],[4,3]] 
let map = new Map(pairs); 
console.log(map); // Map(3) { '1'=>0, '2'=>1, '3'=>2 ,4=>3}

迭代器协议

迭代器是一次性使用对象,它的API使用next方法进行迭代遍历,next一次,则返回一个迭代结果对象,包含迭代器下一个返回值,不调用next就不知道当前迭代器位置。

next方法

调用next方法返回对象的属性:done和value
done是用于判断是否还有下一个值的布尔(有则false,无则true),value是下一个值,没有则是undefined。

let arr = ['foo', 'bar']; 
let iter1 = arr[Symbol.iterator](); 
console.log(iter1.next()); // { done: false, value: 'foo' } 
console.log(iter1.next()); // { done: false, value: 'bar' }
console.log(iter1.next()); // {value: undefined, done: true}

自定义迭代器

class Counter { // Counter 的实例应该迭代 limit 次constructor(limit) { this.count = 1; this.limit = limit; } next() { if (this.count <= this.limit) { return { done: false, value: this.count++ }; } else { return { done: true, value: undefined }; } } [Symbol.iterator]() { return this; } 
} 
let counter = new Counter(3); 
for (let i of counter) { console.log(i); 
} 
// 1
// 2
// 3

可以自己实现一个自定义迭代,里面有迭代的返回条件以及对应的返回值。这里就不赘述了。

提前终止迭代器

不想遍历到终止,想要提前终止怎么办?
for-of可以通过break、continue、return、throw提前退出。也可以自己设置一些终止条件来进行判断是否终止。
如果我们不想终止迭代,但是想暂停某个自定义迭代函数块,在特定条件下暂停或者继续,就可以用到生成器来进行操作。

生成器

定义

ES6新增结构,拥有在一个函数块内暂停和恢复代码执行的能力。这种新能力具有深远的影响,比如,使用生成器可以自定义迭代器和实现协程。

// 生成器函数声明
function* generatorFn() {} 
// 生成器函数表达式
let generatorFn = function* () {} 
// 作为对象字面量方法的生成器函数
let foo = { * generatorFn() {} 
} 
// 作为类实例方法的生成器函数
class Foo { * generatorFn() {} 
} 
// 作为类静态方法的生成器函数
class Bar { static * generatorFn() {} 
}

注:箭头函数不能用来定义生成器函数。
生成器函数声明就是在普通函数名前加*即可。而调用这个生成器会出现一个生成器对象,该对象初始为暂停执行状态。同时它也实现了Iterator接口,也有next方法,调用next会让生成器开始或恢复执行,同样next有两个属性done和value,当生成器函数体为空时done为true,value为undefined,想要修改默认value,可通过修改返回值来不让空函数体默认为undefined。

function* makeRangeIterator(start = 0, end = Infinity, step = 1) {let iterationCount = 0;for (let i = start; i < end; i += step) {iterationCount++;yield i;}return iterationCount;
}

上述代码中的yield关键字,用于生成器中断执行和继续执行。通过yield关键字退出的生成器,返回的done是false。

function* a(){yield;
}
console.log(a().next())//done:false,value:undefined

注:yield关键字只能在生成器函数内部使用。

yield可以干嘛

生成器对象可以作为可迭代对象
function* generatorFn(){yield 1; yield 2; yield 3; 
} 
for (const x of generatorFn()) { console.log(x); 
} 
// 1 
// 2 
// 3
使用 yield 实现输入和输出
function* generatorFn(initial) { console.log(initial); console.log(yield); console.log(yield); 
} 
let generatorObject = generatorFn('foo'); 
generatorObject.next('bar'); // foo 
generatorObject.next('baz'); // baz 
generatorObject.next('qux'); // qux

也可以同时输入输出

function* generatorFn() { return yield 'foo'; 
} 
let generatorObject = generatorFn(); 
console.log(generatorObject.next()); // { done: false, value: 'foo' } 
console.log(generatorObject.next('bar')); // { done: true, value: 'bar' }
产生可迭代对象
function* generatorFn() { yield* [1, 2, 3]; 
} 
let generatorObject = generatorFn(); 
for (const x of generatorFn()) { console.log(x); 
} 
// 1 
// 2 
// 3
使用 yield*实现递归算法
function* nTimes(n) { if (n > 0) { yield* nTimes(n - 1); yield n - 1; } 
} 
for (const x of nTimes(3)) { console.log(x); 
} 
// 0 
// 1 
// 2

提前终止生成器

return方法

function* generatorFn() { for (const x of [1, 2, 3]) { yield x; } 
} 
const g = generatorFn(); 
console.log(g); // generatorFn {<suspended>} 
console.log(g.return(4)); // { done: true, value: 4 } 
console.log(g); // generatorFn {<closed>}

throw方法

function* generatorFn() { for (const x of [1, 2, 3]) { yield x; } 
} 
const g = generatorFn(); 
console.log(g); // generatorFn {<suspended>} 
try { g.throw('foo'); 
} catch (e) { console.log(e); // foo 
} 
console.log(g); // generatorFn {<closed>}

http://www.ppmy.cn/news/1222839.html

相关文章

VSCode打开Json 文件格式化

在VSCode中打开JSON文件时&#xff0c;你可以使用以下步骤来格式化JSON并显示为多行&#xff1a; 使用快捷键&#xff1a; 在打开的JSON文件中&#xff0c;使用快捷键格式化文档。 Windows/Linux&#xff1a;Shift Alt FmacOS&#xff1a;Shift Option F 右键菜单&#xff…

linux高级篇基础理论二(详细文档、LAMP、SHELL、sed正则表达式)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a; 小刘主页 ♥️不能因为人生的道路坎坷,就使自己的身躯变得弯曲;不能因为生活的历程漫长,就使求索的 脚步迟缓。 ♥️学习两年总结出的运维经验&#xff0c;以及思科模拟器全套网络实验教程。专栏&#xff1a;云计算技…

修改CentOS默认mail发件名称

修改CentOS默认的邮件发件名称 要修改CentOS默认的邮件发件名称&#xff0c;可以按照以下步骤进行操作&#xff1a; 打开终端或SSH连接到CentOS服务器。使用root或具有管理员权限的用户登录。编辑postfix配置文件。在终端中输入以下命令&#xff1a; vi /etc/postfix/main.cf…

【Linux】vscode远程连接ubuntu失败

VSCode远程连接ubuntu服务器 这部分网上有很多&#xff0c;都烂大街了&#xff0c;自己搜吧。给个参考连接&#xff1a;VSCode远程连接ubuntu服务器 注意&#xff0c;这里我提前设置了免密登录。至于怎么设置远程免密登录&#xff0c;可以看其它帖子&#xff0c;比如这个。 …

通付盾Web3专题 | KYT/AML:Web3合规展业的必要条件

与传统证券一样&#xff0c;基于区块链技术发展出来的虚拟资产交易所经历了快速发展而缺乏有效监管的行业早期。除了科技光环加持的各种区块链项目方、造富神话之外&#xff0c;交易所遭到黑客攻击、内部偷窃作恶、甚至经营主体异常而致使投资人血本无归的案例亦令人触目惊心。…

Python基础入门----如何通过conda搭建Python开发环境

文章目录 使用 conda 搭建Python开发环境是非常方便的,它可以帮助你管理Python版本、依赖库、虚拟环境等。以下是一个简单的步骤,演示如何通过 conda 搭建Python开发环境: 安装conda: 如果你还没有安装 conda,首先需要安装Anaconda或Miniconda。Anaconda是一个包含很多数据…

k8s之HPA

HPA&#xff08;Horizontal Pod Autoscaling&#xff09;Pod 水平自动伸缩&#xff0c;Kubernetes 有一个 HPA 的资源&#xff0c;HPA 可以根据 CPU 利用率自动伸缩一个 Replication Controller、Deployment 或者Replica Set 中的 Pod 数量。 &#xff08;1&#xff09;HPA 基于…