迭代器和生成器
JS的迭代器(Iterator)和生成器(Generator)是ECMAScript 2015(通常称为ES6)引入的两个重要特性,它们的引入主要是为了解决JavaScript中异步编程的一些问题,以及提供一种更简洁明了地处理集合数据的方式。
为什么把他们呢两个放在一起来看,因为生成器在用的时候可以依赖迭代器🦍🦧
迭代器(Iterator)
背景历史和作用:
在ES6之前,JavaScript中的异步编程主要依赖于回调函数,这会导致所谓的“回调地狱”问题,代码嵌套层次过多,难以阅读和维护。同时,处理像数组或对象这类集合数据时也不够方便。
迭代器模式是一种设计模式,它提供了一种方法顺序访问一个集合对象中各个元素, 而又不需要暴露该对象的内部表示。ES6中,引入了迭代器协议,并且内置了对迭代器的支持。迭代器协议定义了产生一系列值(无论是有限个还是无限个)的标准方式。
一个对象若要成为迭代器,需要实现next()方法,这个方法需要返回一个对象,该对象包含两个属性:value表示当前的数据成员的值,done是一个布尔值,表示遍历是否结束。
详细理解:
迭代器使得数据集合的遍历变得更加抽象和通用,无需关心数据的内部结构。比如说,数组是JavaScript中最常用的数据结构之一,for…of循环可以通过数组对象的迭代器来遍历数组元素,这是因为数组是可迭代的(iterable),即实现了[Symbol.iterator]方法。
let arr = [1, 2, 3, 4, 5];
let it = arr[Symbol.iterator](); // 获取迭代器对象
console.log(it.next()); // { value: 1, done: false }
console.log(it.next()); // { value: 2, done: false }
// ...
生成器(Generator)
背景历史和作用:
生成器是ES6中新增的另一个重要特性,可以理解为一种能够暂停执行和恢复执行的函数。生成器的引入,同样是为了解决异步编程的问题,并且进一步地简化迭代器的创建过程。
生成器函数在定义时用function*声明,函数内部可以使用yield关键字暂停函数执行并返回一个值,下一次调用生成器的next()方法时,会从暂停的位置继续执行。
详细理解:
生成器在处理异步操作时有极大的优势,它可以在某个操作未完成时暂停代码执行,等到异步操作完成再继续。这种能力让生成器看起来像是能够异步返回值的函数。
function* generatorExample() {
yield '🥶';
yield 2;
return 'jack';
}
let gen = generatorExample();
console.log(gen.next()); // { value: '🥶', done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 'jack', done: true }
这里调用生成器函数产生了一个生成器对象这个对象是一个迭代器,当然,数组、Map等也是迭代器 所以生成器可以配合for...of
循环使用,使得其实现的迭代逻辑更自然:
for (let value of generatorExample()) {
console.log(value);
}
// 输出: 🥶 2
注意,生成器的return语句返回的值在for...of
循环中是不会被遍历的,因为当done为true时循环就会结束。
生成器和迭代器最大的不同在于:迭代器通常是用来遍历数据集合的工具,而生成器提供了更强大的控制流程的能力,它不仅可以用于遍历数据,还能用于异步编程的场景。
随后,在ES7/ES2016引入了async/await
,进一步改进了异步编程体验,async/await
可以看做是基于Promise和生成器的一个更高层次的抽象。