摘要
文章详细阐述了 JavaScript 中迭代器与生成器的概念、协议、应用场景、相互关系,还介绍了可迭代对象相关知识,最后展示了如何用生成器优化可迭代对象写法,并提及异步代码处理方案及迭代器助手提案。
1. 迭代器相关内容
1.1 迭代器的概念
迭代器(iterator)用于在容器对象(如数组、链表等)上进行遍历。在 JavaScript 中,迭代器是一个遵循迭代器协议的对象,其关键在于具有特定的next
方法。这个next
方法在调用时会返回一个包含value
(当前产生的值)和done
(表示是否所有值都已迭代完毕的布尔值)的对象。例如创建一个基础的迭代器:
const iterator = {
next: function() {
return { done: true, value: 'coderwhy' }
}
};
1.2 可迭代对象与迭代器协议
可迭代对象和迭代器是不同的概念,迭代协议分为可迭代协议和迭代器协议。
1.2.1 可迭代协议
一个对象要被for...of
循环使用,必须实现可迭代协议,即具有[Symbol.iterator]
方法。可迭代对象可以看作是一种抽象的数据集合,可以进行迭代。例如:
const iterableObj = {
names: ["coderwhy", "XiaoYu", "JS"],
[Symbol.iterator]: function() {
let index = 0;
return {
next: () => {
if (index < this.names.length) {
return { done: false, value: this.names[index ++] };
} else {
return { done: true, value: undefined };
}
}
};
}
};
1.2.2 迭代器协议
迭代器对象必须实现next
方法,返回{ value, done }
。迭代器对象更倾向于描述一个“过程”,是逐次产出元素的工具。
1.3 原生迭代器对象
JavaScript 中部分原生对象如 String、Array、TypeArray、Map、Set 以及 Intl.Segments 已经实现了可迭代协议,会生成一个迭代器对象。这就是为什么数组等数据结构可以直接进行迭代操作。
1.4 自定义类对象可迭代性
在面向对象开发中,若希望自定义类创建的对象可迭代,需要在类中添加[Symbol.iterator]
方法。例如定义Classroom
类时添加该方法,就可以使Classroom
类创建的对象可迭代。
2. 生成器相关内容
2.1 生成器的基本概念
生成器(Generator)是 ES6 引入的一种特殊函数类型,通过在function
后加*
定义。在生成器函数内部,可以使用yield
关键字暂停函数执行并返回一个值。生成器函数调用时会返回一个生成器对象,该对象可调用next
方法来控制函数的执行与暂停。例如:
function* foo() {
console.log("函数开始执行~");
const value1 = 100;
console.log("第一段代码:", value1);
yield;
const value2 = 200;
console.log("第二段代码:", value2);
yield;
const value3 = 300;
console.log("第三段代码:", value3);
yield;
console.log("函数执行结束~");
return "123";
}
const generator = foo();
generator.next();
generator.next();
generator.next();
generator.next();
2.2 生成器函数执行流程
生成器函数返回的生成器对象是一个特殊的迭代器,其next
方法遵守迭代器对象的返回值标准。yield
关键字有多种用法,如[rv] = yield [expression]
,可以使生成器函数在每个yield
处暂停执行,并且yield
具备双向通信功能。
2.3 yield 错位双向传递
生成器函数和调用者是交替执行的,这种机制导致了yield
的输出(返回值给调用者)和next(value)
的输入(传入值给生成器)发生在两个不同的阶段,即错位传递。这是由生成器的暂停和恢复机制决定的,如果没有这种错位传递,在单线程的 JavaScript 中会出现多种问题。
2.4 生成器的其他方法使用
2.4.1 生成器的终止执行
Generator.prototype.return
方法用于提前终止生成器函数的执行,同时可以指定一个返回值。调用return(value)
后,生成器会立即停止执行,后续的next()
调用不会再执行生成器函数内部的代码。
2.4.2 生成器的抛出异常
Generator.prototype.throw()
方法用于向生成器函数内部抛出一个错误,在生成器暂停的yield
表达式处抛出异常。根据生成器内部是否捕获异常,会有不同的处理结果。
3. 可迭代对象写法的优化
3.1 一般优化
通过学习生成器之后,可以简化可迭代对象的写法。例如使用生成器函数替代[Symbol.iterator]
方法,还可以使用yield*
语法糖进一步精简代码。
3.2 迭代数字案例
在迭代数字的案例中,使用生成器的代码相比迭代器更加简洁,这体现了生成器是迭代器的更高抽象层次封装。
3.3 自定义类对象优化
对于自定义类对象的可迭代性,也可以通过生成器进行完善,如在Classroom
类的[Symbol.iterator]
方法中使用yield*
来简化代码。
4. 异步代码的处理方案
4.1 方式 1:层层嵌套
使用 Promise 的then
、catch
方法处理异步数据获取时,如果多次处理数据,很容易形成回调地狱,例如多层嵌套的网络请求。
4.2 方式 2:链式调用
相对于回调地狱,链式调用是一种更好的方式,then
方法可以接收上一阶段返回的内容进行调用。
4.3 方案 3:Generator 方案
结合生成器函数,通过yield
可以与服务器进行交互,实现同步代码的效果。但手动执行生成器函数时会面临嵌套问题,因此可以封装一个工具函数execGenerator
来自动执行生成器函数。此外,还有co
这个 npm 库也实现了类似功能,通过生成器函数实现协程,简化异步代码的编写。
5. 迭代器助手提案
「 Stage 4 」迭代器助手
提案引入了一系列新的迭代器原型方法,如map
、filter
等。该提案将使迭代器在处理大型或无限可枚举数据集时更加方便,有望在 ES16 纳入规范并发布。