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

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

理解迭代

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

javascript 复制代码
for(let i=0;i<5;i++){
	console.log(i)
}

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

javascript 复制代码
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]默认属性,来判断是否存在工厂函数。

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

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

javascript 复制代码
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 *(生成器)

实例:

javascript 复制代码
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。

javascript 复制代码
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}

自定义迭代器

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

javascript 复制代码
// 生成器函数声明
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。

javascript 复制代码
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。

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

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

yield可以干嘛

生成器对象可以作为可迭代对象
javascript 复制代码
function* generatorFn(){
 yield 1; 
 yield 2; 
 yield 3; 
} 
for (const x of generatorFn()) { 
 console.log(x); 
} 
// 1 
// 2 
// 3
使用 yield 实现输入和输出
javascript 复制代码
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

也可以同时输入输出

javascript 复制代码
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' }
产生可迭代对象
javascript 复制代码
function* generatorFn() { 
 yield* [1, 2, 3]; 
} 
let generatorObject = generatorFn(); 
for (const x of generatorFn()) { 
 console.log(x); 
} 
// 1 
// 2 
// 3
使用 yield*实现递归算法
javascript 复制代码
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方法

javascript 复制代码
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方法

javascript 复制代码
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>}
相关推荐
别拿曾经看以后~2 分钟前
【el-form】记一例好用的el-input输入框回车调接口和el-button按钮防重点击
javascript·vue.js·elementui
川石课堂软件测试7 分钟前
性能测试|docker容器下搭建JMeter+Grafana+Influxdb监控可视化平台
运维·javascript·深度学习·jmeter·docker·容器·grafana
Source.Liu16 分钟前
【用Rust写CAD】第二章 第四节 函数
开发语言·rust
monkey_meng16 分钟前
【Rust中的迭代器】
开发语言·后端·rust
余衫马19 分钟前
Rust-Trait 特征编程
开发语言·后端·rust
JerryXZR22 分钟前
前端开发中ES6的技术细节二
前端·javascript·es6
monkey_meng23 分钟前
【Rust中多线程同步机制】
开发语言·redis·后端·rust
Jacob程序员25 分钟前
java导出word文件(手绘)
java·开发语言·word
小白学大数据31 分钟前
正则表达式在Kotlin中的应用:提取图片链接
开发语言·python·selenium·正则表达式·kotlin
VBA633733 分钟前
VBA之Word应用第三章第三节:打开文档,并将文档分配给变量
开发语言