迭代器和生成器

Yaurora

在了解for…of遍历的时候,简单的提到了可迭代对象这个词,这里来具体了解一下

前言

前面说到可迭代对象是含有Symbol.iterator属性的对象。具体说到可迭代对象,就要涉及到迭代器和生成器。

迭代器

迭代器本身是一个对象,这个对象是具有next()方法返回结果对象。这个返回对象是一个{value: any, done: boolean}格式的对象。

一旦创建迭代器对象,这个对象可以通过重复调用next()方法显示地迭代。迭代一个迭代器对象,就成为消耗了这个迭代器,因为它通常只能执行一次,在产生了终值以后,调用next方法会返回一个{done: true, value: undefined}的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function createIterator(items) {
let index = 0
return {
next(){
let result = {value: undefined, done: true}
if (index < items.length){
result = {value: items[index], done: false}
index += 1
}
return result
}
}
}

const it = createIterator([1,2,3])
console.log(it.next()) // {value: 1, done: false}
console.log(it.next()) // {value: 2, done: false}
console.log(it.next()) // {value: 3, done: false}
console.log(it.next()) // {value: undefined, done: true}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function createRangeIterator(start = 0,end = Infinity,step = 1){
let value = start
const iterator = {
next(){
let result = {value: undefined, done: true}
if (value <= end){
result = {value: value, done: false }
value += step
}
return result
}
}
return iterator
}

const rit = createRangeIterator(1, 7, 2)
let result = rit.next()
while (!result.done){
console.log(result.value) // 1 3 5 7
result = rit.next() // 重复调用, 显示迭代
}

每次调用迭代器的next()方法,都会返回下一个对象,直到数据集被用尽

生成器

自定义的迭代器,需要我们显示的维护其内部的状态,故需要谨慎创建。生成器函数,可以帮助我们创建一个迭代器对象

生成器函数,使用function* 语法, 返回一种成为generator的迭代器。调用生成器的下一个方法消耗值时,generator函数将执行,直到遇到yield关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function* createRangeIterator(start = 0, end = Infinity, step = 1){
for(let i = start; i <= end; i += step){
yield i
}
}

const rit = createRangeIterator(1, 7, 2)
let result = rit.next()
while(!result.done){
console.log(result.value) // 1 3 5 7
result = rit.next()
}

// 使用for...of语法遍历
const rit1 = createRangeIterator(1, 7, 2)
for(const item of rit1){
console.log(item) // 1 3 5 7
}

使用生成器函数创建的迭代器,比之前自定义的生成器精简了不少

高级生成器

生成器中next()方法也是可以接受一个参数,用于修改生成器内部的状态。传递给next()方法的参数会被yield接收。但传递给第一next()方法的参数会被忽略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function *createIterator(){
const param1 = yield 1
console.log('param1', param1) // param1 p2
const param2 = yield 2
console.log('param2', param2) // param2 p3
const param3 = yield 3
console.log('param3', param3) // param3 p4
}

const it = createIterator()
console.log('result1', it.next('p1')) // result1 {value:1 ,done: false}
console.log('result2', it.next('p2')) // result2 {value:2 ,done: false}
console.log('result3', it.next('p3')) // result3 {value:3 ,done: false}
console.log('result4', it.next('p4')) // result4 {value:undefined ,done: true}

// 最终输出结果顺序如下
/*
result1 {value: 1, done: false}
param1 p2
result2 {value:2 ,done: false}
param2 p3
result3 {value:3 ,done: false}
param3 p4
result4 {value:undefined ,done: true}
*/

// 另外,对于生成器生成的迭代器,我们可以使用for...of语法
const it2 = createIterator()
for(const item of it2){
console.log(item) // 1 2 3
}

分析

  • 关于参数接收问题, 我们可以看见每次调用next(),显示直接返回结果,拿取下一次调用next()方法中的参数作为当前yield 的返回结果
  • yield返回参数后,后面的代码不会执行了,只有当下次调用next()时,才会执行到下一个yield的位置
  • 结合以上两个特性,我们可以看出它为什么丢弃了第一次的参数

可迭代对象(iterables)

为了实现可迭代,一个对象必须实现 @@iterator方法,这意味着这个对象(或其原型链中的任意一个对象)必须具有一个带 Symbol.iterator键(key)的属性。

对于只迭代一次的iterables,通常从@@iterator返回本身, 对于可以迭代多次的,必须每次调用@@iterator方法返回一个新的迭代器

常用内置可迭代对象: StringArrayTypedArrayMapSet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const myIterable = {
*[Symbol.iterator](){
for (let i = 0; i < 3; i++){
yield i
}
}
}

for(const item of myIterable){
console.log(item) // 1 2 3
}

// 和使用原生迭代器的方式对比一下
const myIterable1 = {
[Symbol.iterator]: () => {
let index = 0
const end = 3
return {
next(){
let result = {value: undefined, done: true}
if (index < end){
result = {value: index, done: false}
index += 1
}
return result
}
}
}
}

console.log([...myIterable1]) // [1, 2, 3]
  • 标题: 迭代器和生成器
  • 作者: Yaurora
  • 创建于 : 2023-03-13 10:22:12
  • 更新于 : 2023-03-14 00:04:28
  • 链接: https://jingyu.life/2023/03/13/node/iterator-generator/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。