1. 迭代器(Iterator)
1.1 迭代器是什么
迭代器 是一个用于访问数据集合(如数组、字符串、Map、Set 等)中每个元素的对象,它提供了一种统一的遍历机制。通过迭代器,开发者可以按照一定的顺序访问集合中的元素,而不必关心集合的内部结构。
迭代器协议要求一个对象至少包含一个 next() 方法,next() 方法返回一个对象,该对象包含以下两个属性:
value:表示当前元素的值。done:一个布尔值,表示是否已经遍历到集合的末尾。若遍历完毕,则done为true,否则为false。
1.2 迭代器的基本用法
在 JavaScript 中,所有的可迭代对象(如数组、字符串、Set、Map 等)都具有内建的迭代器。你可以通过 Symbol.iterator 获取这些对象的迭代器,并通过 next() 方法遍历它们。
示例:数组的迭代器
let arr = [1, 2, 3];
let iterator = arr[Symbol.iterator](); // 获取数组的迭代器
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
在这个例子中,arr[Symbol.iterator]() 返回了一个迭代器,每次调用 next() 都会返回集合的下一个元素,直到 done 为 true 表示迭代结束。
1.3 自定义迭代器
你也可以自定义一个对象,使其成为可迭代对象,只要它实现了迭代器协议,即具有 next() 方法。
示例:自定义对象的迭代器
let myObj = {
from: 1,
to: 5,
[Symbol.iterator]: function() {
let current = this.from;
let last = this.to;
return {
next() {
if (current <= last) {
return { value: current++, done: false };
} else {
return { value: undefined, done: true };
}
}
};
}
};
for (let value of myObj) {
console.log(value); // 输出 1, 2, 3, 4, 5
}
在这个例子中,myObj 是一个自定义对象,我们通过定义 [Symbol.iterator] 方法,使得 myObj 成为一个可迭代对象。
1.4 迭代器常见方法
next():返回当前元素,并移动到下一个元素,直到集合遍历完毕。return():通常用于提前退出迭代。它用于终止迭代并可以返回一个值。throw():允许在迭代过程中抛出异常。
1.5 迭代器常见场景
- 自定义数据结构的遍历: 如果你设计了一个自定义的数据结构,想要让它像内建的数组、集合那样可以通过
for...of语法遍历,你可以手动实现迭代器。 - 统一数据结构的遍历: 迭代器允许你使用相同的语法遍历不同类型的数据结构(如数组、Set、Map 等),这使得你可以对不同数据结构进行统一的操作。
- 惰性加载数据: 迭代器非常适合惰性加载数据,即按需生成和返回数据。这在处理大量数据时尤为重要,可以避免一次性加载所有数据,占用过多内存。
2. 生成器(Generator)
2.1 生成器是什么
生成器 是一种特殊的函数,它可以暂停执行并且恢复执行,从而允许我们按需生成一系列值。生成器函数使用 function* 定义,生成器函数内部可以使用 yield 表达式来生成值,并且暂停执行。
生成器返回一个迭代器对象,可以通过 next() 方法来控制生成器的执行。
生成器函数是通过 function* 语法定义的,函数内部使用 yield 表达式来生成值。
2.2 生成器的基本用法
生成器函数是通过 function* 语法定义的,而生成器的调用返回一个迭代器对象,你可以用 next() 方法迭代这个生成器。
示例:基本的生成器函数
function* myGenerator() {
yield 1;
yield 2;
yield 3;
}
let gen = myGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }
2.3 yield 语法
-
yield用于生成一个值,并且暂停生成器函数的执行。生成器函数会从yield处返回控制权,直到下一次调用next()。 -
可以通过
next()向生成器传递值,并恢复执行。next()方法的返回值包含两个属性: -
value:生成器当前返回的值。done:表示生成器是否已完成,done: true表示没有更多值,done: false表示仍有更多值。
示例:传递值给生成器
function* myGenerator() {
let x = yield 1;
console.log(x); // 输出 2
let y = yield 2;
console.log(y); // 输出 3
yield 3;
}
let gen = myGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next(2)); // { value: 2, done: false }, 输出 2
console.log(gen.next(3)); // { value: 3, done: false }, 输出 3
console.log(gen.next()); // { value: undefined, done: true }
在这个例子中,我们通过 gen.next(2) 向生成器传递值,生成器中的 yield 会接收到这些值。
2.4 生成器的 return() 和 throw() 方法
生成器除了能够通过 yield 暂停和恢复执行,还可以通过 return() 提前退出并返回一个值,或者通过 throw() 抛出异常。
return() 方法
function* myGenerator() {
yield 1;
yield 2;
return 3;
}
let gen = myGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.return(4)); // { value: 4, done: true }
console.log(gen.next()); // { value: undefined, done: true }
throw() 方法
function* myGenerator() {
try {
yield 1;
yield 2;
} catch (e) {
console.log(e); // 输出 'Error!'
}
}
let gen = myGenerator();
console.log(gen.next()); // { value: 1, done: false }
gen.throw('Error!'); // 捕获到错误
2.5 生成器常见场景
生成器可以用于懒加载、大数据处理、协程等场景。它的优势在于每次调用 next() 时才计算下一个值,能够有效节省内存,并提高性能。
- 懒加载和数据流:通过生成器处理大数据流,只有在需要时才生成数据。
- 协程:生成器能够通过
yield和next()实现异步流程控制,使得代码更简洁易懂。
3. 迭代器与生成器的对比
| 特性 | 迭代器 (Iterator) | 生成器 (Generator) |
|---|---|---|
| 定义 | 迭代器是一个对象,提供遍历集合元素的机制。 | 生成器是一个特殊函数,返回一个迭代器对象。 |
| 语法 | 通过实现 next() 方法的对象来创建。 | 使用 function* 和 yield 来创建。 |
| 返回值 | next() 返回 { value, done } 对象。 | next() 返回 { value, done } 对象。 |
| 控制流 | 迭代器的控制流由调用者管理。 | 生成器可以通过 yield 暂停和恢复执行。 |
| 状态管理 | 迭代器的状态通常由外部逻辑控制。 | 生成器内部管理状态,自动保存和恢复。 |
| 懒加载(惰性求值) | 不一定是懒加载,通常一次性获取所有元素。 | 生成器是惰性求值,按需生成元素。 |
| 用法 | 常用于自定义对象的迭代逻辑。 | 生成器函数是创建迭代器的简便方法,适合处理大量数据或异步流程。 |
4. 总结
4.1 迭代器与生成器的异同
相同点:
- 都实现了迭代器协议,具有
next()方法来返回数据。 - 都能够按需返回数据,支持惰性求值。
不同点:
- 迭代器:由一个对象实现,通常需要手动管理状态。
- 生成器:由一个生成器函数实现,语法更加简洁,自动处理暂停和恢复执行的过程。
4.2 性能与应用场景的选择
-
生成器在处理大数据集或流数据时特别有用,尤其是在异步编程中,生成器提供了一种更清晰的控制流。
-
普通迭代器适合简单的按顺序遍历的数据结构,通常不涉及复杂的暂停或恢复执行的需求。
-
生成器函数的创建和调用比普通迭代器稍微有些性能开销,但这个开销通常可以忽略不计,特别是在需要处理复杂迭代逻辑(如异步数据处理)时,生成器的优势更为明显。