fe-interview前端迭代器模式:集合遍历访问
引言:为什么需要迭代器模式?
在日常前端开发中,我们经常需要处理各种数据集合的遍历操作。从简单的数组遍历到复杂的树形结构处理,如何优雅、高效地访问集合元素成为了每个开发者必须面对的挑战。传统的for循环虽然功能强大,但在复杂场景下往往显得笨重且难以维护。
迭代器模式(Iterator Pattern)正是为了解决这一问题而生的设计模式。它提供了一种统一的方式来遍历各种集合对象,而无需暴露其内部表示。本文将深入探讨迭代器模式在前端开发中的应用,结合fe-interview项目中的实际面试题,为你揭示集合遍历的最佳实践。
什么是迭代器模式?
核心概念
迭代器模式是一种行为设计模式,它允许你顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。在JavaScript中,迭代器模式通过迭代器协议(Iterator Protocol) 来实现。
// 迭代器协议的基本结构
const iterator = {
next() {
return {
value: any, // 当前迭代的值
done: boolean // 是否迭代完成
}
}
}
迭代器 vs 可迭代对象
理解迭代器模式,首先要区分两个重要概念:
| 概念 | 描述 | 示例 |
|---|---|---|
| 迭代器(Iterator) | 具有next()方法的对象,用于遍历集合 | array[Symbol.iterator]() |
| 可迭代对象(Iterable) | 实现了[Symbol.iterator]方法的对象 | Array, Map, Set, String |
// 可迭代对象示例
const iterable = {
[Symbol.iterator]() {
let step = 0;
return {
next() {
step++;
if (step <= 3) {
return { value: step, done: false };
}
return { value: undefined, done: true };
}
};
}
};
for (const value of iterable) {
console.log(value); // 1, 2, 3
}
JavaScript内置迭代器实现
数组迭代器
数组是JavaScript中最常用的可迭代对象,其迭代器实现非常高效:
const array = [1, 2, 3];
const iterator = array[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 }
Map和Set迭代器
ES6引入的Map和Set也实现了迭代器协议:
// Map迭代器
const map = new Map([['a', 1], ['b', 2]]);
for (const [key, value] of map) {
console.log(key, value); // a 1, b 2
}
// Set迭代器
const set = new Set([1, 2, 3]);
for (const value of set) {
console.log(value); // 1, 2, 3
}
字符串迭代器
字符串也是可迭代对象,可以正确处理Unicode字符:
const str = "前端";
for (const char of str) {
console.log(char); // "前", "端"
}
自定义迭代器实现
实现一个范围迭代器
让我们实现一个简单的数字范围迭代器:
class Range {
constructor(start, end, step = 1) {
this.start = start;
this.end = end;
this.step = step;
}
[Symbol.iterator]() {
let current = this.start;
const end = this.end;
const step = this.step;
return {
next() {
if ((step > 0 && current <= end) || (step < 0 && current >= end)) {
const value = current;
current += step;
return { value, done: false };
}
return { value: undefined, done: true };
}
};
}
}
// 使用示例
const range = new Range(1, 5);
for (const num of range) {
console.log(num); // 1, 2, 3, 4, 5
}
树形结构迭代器
处理树形数据时,迭代器模式特别有用:
class TreeNode {
constructor(value, children = []) {
this.value = value;
this.children = children;
}
// 深度优先遍历迭代器
[Symbol.iterator]() {
const stack = [this];
return {
next() {
if (stack.length === 0) {
return { done: true, value: undefined };
}
const node = stack.pop();
// 将子节点逆序压入栈中,保证正序遍历
for (let i = node.children.length - 1; i >= 0; i--) {
stack.push(node.children[i]);
}
return { done: false, value: node.value };
}
};
}
}
// 使用示例
const tree = new TreeNode('root', [
new TreeNode('child1', [
new TreeNode('grandchild1'),
new TreeNode('grandchild2')
]),
new TreeNode('child2')
]);
for (const value of tree) {
console.log(value); // root, child1, grandchild1, grandchild2, child2
}
生成器函数与迭代器
ES6的生成器函数极大地简化了迭代器的创建:
基本生成器
function* numberGenerator() {
yield 1;
yield 2;
yield 3;
}
const generator = numberGenerator();
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: 3, done: false }
console.log(generator.next()); // { value: undefined, done: true }
无限序列生成器
function* fibonacci() {
let [prev, curr] = [0, 1];
while (true) {
yield curr;
[prev, curr] = [curr, prev + curr];
}
}
const fib = fibonacci();
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
console.log(fib.next().value); // 3
console.log(fib.next().value); // 5
异步迭代器
ES2018引入了异步迭代器,用于处理异步数据流:
async function* asyncNumberGenerator() {
for (let i = 0; i < 3; i++) {
// 模拟异步操作
await new Promise(resolve => setTimeout(resolve, 100));
yield i;
}
}
(async () => {
for await (const num of asyncNumberGenerator()) {
console.log(num); // 0, 1, 2 (每隔100ms输出)
}
})();
迭代器模式在实际项目中的应用
数据处理管道
迭代器模式可以构建高效的数据处理管道:
function* filter(iterable, predicate) {
for (const item of iterable) {
if (predicate(item)) {
yield item;
}
}
}
function* map(iterable, mapper) {
for (const item of iterable) {
yield mapper(item);
}
}
function* take(iterable, count) {
let taken = 0;
for (const item of iterable) {
if (taken++ >= count) break;
yield item;
}
}
// 使用管道处理数据
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const result = take(
map(
filter(numbers, n => n % 2 === 0),
n => n * 2
),
3
);
console.log([...result]); // [4, 8, 12]
分页数据加载
迭代器模式非常适合处理分页数据:
async function* paginatedData(fetchPage, pageSize = 10) {
let page = 1;
let hasMore = true;
while (hasMore) {
const data = await fetchPage(page, pageSize);
if (data.length === 0) {
hasMore = false;
break;
}
for (const item of data) {
yield item;
}
page++;
}
}
// 模拟API调用
async function fetchPage(page, pageSize) {
// 模拟网络延迟
await new Promise(resolve => setTimeout(resolve, 100));
const start = (page - 1) * pageSize + 1;
const end = start + pageSize - 1;
return Array.from({ length: pageSize }, (_, i) => start + i);
}
// 使用分页迭代器
(async () => {
const dataIterator = paginatedData(fetchPage, 5);
for await (const item of dataIterator) {
console.log(item); // 1, 2, 3, 4, 5, 6, 7, ...
if (item >= 15) break; // 只取前15条
}
})();
迭代器模式的性能优化
惰性求值(Lazy Evaluation)
迭代器模式天然支持惰性求值,只在需要时才计算值:
function* bigDataGenerator() {
for (let i = 0; i < 1000000; i++) {
yield expensiveCalculation(i);
}
}
function expensiveCalculation(n) {
console.log(`Calculating for ${n}`);
return n * 2;
}
// 只计算前3个值
const iterator = bigDataGenerator();
console.log(iterator.next().value); // Calculating for 0 → 0
console.log(iterator.next().value); // Calculating for 1 → 2
console.log(iterator.next().value); // Calculating for 2 → 4
内存效率对比
与传统数组操作相比,迭代器模式在内存使用上更有优势:
// 传统方式 - 需要创建中间数组
const bigArray = Array.from({ length: 1000000 }, (_, i) => i);
const result = bigArray
.filter(x => x % 2 === 0)
.map(x => x * 2)
.slice(0, 10);
console.log(result); // 创建了多个中间数组,内存占用高
// 迭代器方式 - 惰性计算,内存友好
function* processData(array) {
for (const item of array) {
if (item % 2 === 0) {
yield item * 2;
}
}
}
const iterator = processData(bigArray);
const finalResult = [];
for (let i = 0; i < 10; i++) {
const next = iterator.next();
if (next.done) break;
finalResult.push(next.value);
}
console.log(finalResult); // 相同结果,但内存占用更少
常见面试题解析
题目1:实现一个可迭代的类
问题:创建一个类,使其实例可以被for...of循环遍历。
class CustomCollection {
constructor(data) {
this.data = data;
}
[Symbol.iterator]() {
let index = 0;
const data = this.data;
return {
next() {
if (index < data.length) {
return { value: data[index++], done: false };
}
return { value: undefined, done: true };
}
};
}
}
// 测试
const collection = new CustomCollection([1, 2, 3]);
for (const item of collection) {
console.log(item); // 1, 2, 3
}
题目2:实现zip函数
问题:实现一个zip函数,将多个可迭代对象合并。
function* zip(...iterables) {
const iterators = iterables.map(iterable => iterable[Symbol.iterator]());
while (true) {
const results = iterators.map(iterator => iterator.next());
if (results.some(result => result.done)) break;
yield results.map(result => result.value);
}
}
// 测试
const result = zip([1, 2, 3], ['a', 'b', 'c'], [true, false, true]);
console.log([...result]); // [[1, 'a', true], [2, 'b', false], [3, 'c', true]]
题目3:扁平化嵌套数组
问题:使用迭代器实现数组的深度扁平化。
function* flattenDeep(array) {
for (const item of array) {
if (Array.isArray(item)) {
yield* flattenDeep(item);
} else {
yield item;
}
}
}
// 测试
const nested = [1, [2, [3, 4], 5], 6];
console.log([...flattenDeep(nested)]); // [1, 2, 3, 4, 5, 6]
迭代器模式的最佳实践
1. 错误处理
class SafeIterator {
constructor(iterable) {
this.iterable = iterable;
}
[Symbol.iterator]() {
const iterator = this.iterable[Symbol.iterator]();
return {
next() {
try {
return iterator.next();
} catch (error) {
console.error('Iterator error:', error);
return { done: true, value: undefined };
}
}
};
}
}
2. 资源清理
function* resourceGenerator() {
const resource = acquireResource();
try {
while (resource.hasMore()) {
yield resource.getNext();
}
} finally {
resource.release();
}
}
3. 性能监控
function* monitoredIterator(iterable, name = 'unknown') {
let count = 0;
const startTime = Date.now();
for (const item of iterable) {
count++;
yield item;
}
const endTime = Date.now();
console.log(`Iterator ${name}: ${count} items in ${endTime - startTime}ms`);
}
总结与展望
迭代器模式是现代JavaScript编程中不可或缺的重要模式。通过本文的深入探讨,我们可以看到:
- 统一接口:迭代器模式为各种数据集合提供了统一的遍历接口
- 惰性计算:支持按需计算,提高内存使用效率
- 组合性:可以轻松组合多个迭代操作形成处理管道
- 异步支持:异步迭代器为流式数据处理提供了强大支持
在实际开发中,合理运用迭代器模式可以:
- 提高代码的可读性和可维护性
- 优化内存使用和性能表现
- 增强代码的复用性和扩展性
随着JavaScript语言的不断发展,迭代器模式在前端开发中的应用将会越来越广泛。掌握这一模式,不仅有助于通过技术面试,更能提升你的实际开发能力。
记住:优秀的开发者不是知道所有答案的人,而是知道如何找到最佳解决方案的人。迭代器模式就是你工具箱中又一个强大的工具!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



