Javascript 笔记:迭代器和生成器
迭代器是一种数据遍历的算法和协议,也可以用于程序流程的控制,是 Javascript 中比较重要的知识点。
这篇文章简单记录了迭代器的基本概念和使用的场景。
迭代器
迭代器(Iterator)是 JavaScript 中一种特殊的对象,它提供了一种统一的、通用的方式遍历个各种不同类型的数据结构。可以遍历的数据结构包括:数组、字符串、Set、Map 等可迭代对象。我们也可以自定义实现迭代器,以支持遍历自定义的数据结构。
迭代器是一个具体的对象,这个对象要符合迭代器协议。在JS中,某个对象只有实现了符合特定要求的 next() 方法,这个对象才能成为迭代。
实现原理
在JS中,迭代器的实现原理是通过定义一个特定的next() 方法,该方法在每次迭代中返回一个包含两个属性的对象:done 和 value。
具体来说,next() 方法有如下要求:
- 参数:无参数或者有一个参数。
- 返回值:返回一个应当有以下两个属性的对象。属性值如下:
- done 属性(Boolean
类型):表示迭代是否已经完成。当迭代器遍历完所有元素时,done 为
true,否则为 false。具体解释如下:
- 如果迭代器可以产生序列中的下一个值,则为 false,这等价于没有指定 done 属性。
- 如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 可以省略,如果 value 依然存在,即为迭代结束之后的默认返回值。
- value 属性:包含当前迭代步骤的值,可能是具体的值,也可能是 undefined。每次调用 next() 方法时,迭代器返回下一个值。done 为true时,可以省略。
- done 属性(Boolean
类型):表示迭代是否已经完成。当迭代器遍历完所有元素时,done 为
true,否则为 false。具体解释如下:
举例,为数组创建迭代器
1 | const strArr = ['qian', 'gu', 'yi', 'hao']; |
打印结果: 1
2
3
4
5{"done":false,"value":"qian"}
{"done":false,"value":"gu"}
{"done":false,"value":"yi"}
{"done":false,"value":"hao"}
{"done":true}
这个例子没有实际的意义,只是为了演示迭代器的协议。
可迭代对象
当一个对象实现了 iterable protocol 协议 时,它就是一个可迭代对象。这个对象要求必须实现了 @@iterator 方法,在内部封装了迭代器。我们可以通过 Symbol.iterator 函数调用该迭代器。
当我们使用迭代器的方式去遍历数组、字符串、Set、Map 等数据结构时,这些数据对象就属于可迭代对象。这些数据对象本身,内部就自带了迭代器。
原生可迭代对象
以下这些对象,都是原生可迭代对象:
- String 字符串
- Array 数组
- Map
- Set
- arguments 对象
- NodeList 对象(DOM节点的集合)
原生可迭代对象的内部已经实现了可迭代协议,它们都符合可迭代对象的特征。比如,它们内部都有一个迭代器;他们可以用 for ... of 进行遍历。
可迭代对象的应用场景
可迭代对象有许多应用场景,包括但不仅限于:
1、JavaScript的语法:
- for ... of
- 展开语法 ...
- yield*
- 解构赋值
2、创建一些对象:
- new Map([Iterable]):参数是可选的,可不传参数,也可以传一个可迭代对象作为参数
- new WeakMap([iterable])
- new Set([iterable])
- new WeakSet([iterable])
3、方法调用
- Array.from(iterable):将一个可迭代对象转为数组
- Promise.all(iterable)
- Promise.race(iterable)
将对象封装为可迭代对象
要使一个普通对象可以使用 for ... of
进行遍历,需要实现
Symbol.iterator
方法并返回一个迭代器对象。
1 | const myObj1 = { |
中断迭代器
迭代器在遍历数据对象的过程中,如果我们希望在符合指定条件下停止继续遍历,那么,我们可以使用 break、return、throw 等关键字中断迭代器。其中, break 关键字用得最多。
此外,我们还可在迭代器函数中添加一个名为return()的方法,这个方法的作用是监听迭代器的中断,书写代码的位置与 next()方法并列。
代码举例如下:
1 | const myObj2 = { |
打印结果:
1 | name qianguyihao |
return()
函数中,还需要写
return { done: true }
表示迭代器的使命已结束;如果不写这行则会报错:Uncaught TypeError: Iterator result undefined is not an object
。
生成器
生成器是 ES6 中新增的一种特殊的函数,所以也称为“生成器函数”。它可以更灵活地控制函数什么时候执行, 什么时候暂停等等,控制精度很高。
生成器函数使用 function*
语法编写。最初调用时,生成器函数不执行任何代码,而是返回一个称为
Generator 的迭代器。通过调用生成器的 next() 方法时,Generator
函数将执行,直到遇到 yield 关键字时暂停执行。
可以根据需要多次调用该函数,并且每次都返回一个新的 Generator,但每个 Generator 只能迭代一次。
生成器函数和普通函数的区别
- 生成器函数需要在
function
关键字后面加一个符号*
。 - 生成器函数可以通过
yield
关键字控制函数的执行流程。 - 生成器函数的返回值是一个 生成器(Generator), 生成器是一种特殊的迭代器。
定义生成器函数
如果要定义一个生成器函数,我们需要在 function 单词和函数名之间加一个
*
符号。
*
符号有下面四种写法,最推荐的是第一种写法:
1 | function* generator1() { /*code*/ } // 推荐写法 |
实例:
1 | function* foo() { |
调用 foo()
函数返回一个迭代器,此时代码不会执行,需要调用 next
方法才会执行。
生成器执行流程
- 调用生成器函数返回一个迭代器 iterator1;
- 调用迭代器的 next() 方法;
- 执行生成器函数中的代码,直到遇到 yield 表达式 和 return 关键字或函数结尾;
- yield 表达式会暂停执行代码,而遇到 return 和 函数结尾则结束执行。
代码实例: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 通过 * 符号,定义一个生成器函数
function* foo() {
console.log('1');
yield;
console.log('2');
yield;
console.log('3');
}
const generator = foo(); // 返回一个生成器对象
// 调用生成器的 next()方法,生成器才会执行,直到遇到 yield 后暂停执行
generator.next(); // 这行代码执行后,打印结果是:1
generator.next(); // 这行代码执行后,打印结果是:1 2
generator.next(); // 这行代码执行后,打印结果是:1 2 3
生成器 generator 每调用一次 next() ,foo() 函数里的代码就往下执行一次,直到遇到 yield 后暂停。
传递参数
执行 next() 方法之后会包含两个属性的对象,两个属性分别为
done
和 value
,这与迭代器中 next
方法返回的值是一致的,该
value
的值来自那里呢?实际上,我们通过一定的机制在生成器函数外部和内部之间值。
- yield 表达式: yield 带上表达式,该表达式可以传递给 next 方法的
value
属性上; - next(参数):next方法带上参数,该参数会传递到下一个 yield 表达式的返回值。
传递参数如下所示:
如何中途结束生成器的执行
如果想在中途结束生成器的执行,有三种方式:
- 方式1:return 语句。这个在前面已经讲过。
- 方式2:通过生成器的 return() 函数。
- 方式3:通过生成器的 throw() 函数抛出异常。
1 | // 通过 * 符号,定义一个生成器函数 |
参考: