JavaScript 迭代器
什么是迭代器?
迭代器是JavaScript中一种特殊对象,它提供了一种机制,让我们能够按顺序访问集合中的元素,而无需了解集合的内部结构。迭代器对象通过一个next()
方法实现,每次调用这个方法都会返回集合中的下一个元素。
迭代器是ES6(ECMAScript 2015)引入的新特性,它让我们能够以统一的方式遍历不同类型的数据集合。
迭代器的基本原理
一个迭代器对象必须实现next()
方法,该方法返回一个包含两个属性的对象:
value
: 当前元素的值done
: 一个布尔值,表示是否已经遍历到集合的末尾
当done
为true
时,表示迭代已经完成。
创建一个简单的迭代器
下面是一个简单的迭代器示例,它可以遍历一个数组:
function createArrayIterator(array) {
let index = 0;
return {
next: function() {
if (index < array.length) {
return {
value: array[index++],
done: false
};
} else {
return {
value: undefined,
done: true
};
}
}
};
}
// 使用迭代器
const myArray = [1, 2, 3, 4];
const iterator = createArrayIterator(myArray);
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: 4, done: false }
console.log(iterator.next()); // 输出: { value: undefined, done: true }
在上面的例子中,我们创建了一个函数createArrayIterator
,它接受一个数组作为参数,并返回一个迭代器对象。每次调用next()
方法,迭代器都会返回数组中的下一个元素,并在遍历完所有元素后返回{value: undefined, done: true}
。
可迭代对象(Iterable)
可迭代对象是实现了Symbol.iterator
方法的对象。这个方法必须返回一个迭代器对象。
JavaScript中的许多内置对象都是可迭代的,包括:
- 数组(Array)
- 字符串(String)
- Map
- Set
- TypedArray
- arguments对象
创建一个可迭代对象
下面是一个自定义可迭代对象的示例:
const myIterable = {
data: [10, 20, 30],
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.data.length) {
return { value: this.data[index++], done: false };
} else {
return { value: undefined, done: true };
}
}
};
}
};
// 使用for...of循环遍历可迭代对象
for (const item of myIterable) {
console.log(item); // 依次输出: 10, 20, 30
}
在上面的例子中,我们创建了一个包含Symbol.iterator
方法的对象myIterable
。这个方法返回一个迭代器,使得myIterable
对象可以被for...of
循环遍历。
使用迭代器的常见方式
1. for...of循环
for...of
循环是遍历可迭代对象最简单的方式:
const numbers = [1, 2, 3, 4, 5];
for (const num of numbers) {
console.log(num); // 依次输出: 1, 2, 3, 4, 5
}
2. 展开运算符(Spread Operator)
展开运算符(...
)可以将可迭代对象展开成独立的元素:
const parts = ['shoulders', 'knees'];
const body = ['head', ...parts, 'and', 'toes'];
console.log(body); // 输出: ['head', 'shoulders', 'knees', 'and', 'toes']
3. 解构赋值
解构赋值可以从可迭代对象中提取值:
const [a, b, c] = new Set([1, 2, 3]);
console.log(a); // 输出: 1
console.log(b); // 输出: 2
console.log(c); // 输出: 3
4. Array.from()
Array.from()
方法可以从可迭代对象创建一个新数组:
const map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
const array = Array.from(map);
console.log(array); // 输出: [[1, 'one'], [2, 'two'], [3, 'three']]
迭代器的实际应用
1. 分页数据遍历
假设我们有一个API,每次只返回一页数据。我们可以创建一个迭代器来轻松遍历所有页的数据:
function createPaginationIterator(fetchPage, pageSize) {
let currentPage = 0;
let items = [];
let currentIndex = 0;
let isDone = false;
return {
next: async function() {
if (currentIndex >= items.length) {
if (isDone) {
return { done: true };
}
// 获取下一页数据
const response = await fetchPage(currentPage, pageSize);
items = response.items;
currentIndex = 0;
currentPage++;
if (items.length < pageSize) {
isDone = true;
}
if (items.length === 0) {
return { done: true };
}
}
return {
value: items[currentIndex++],
done: false
};
}
};
}
// 使用示例
async function fetchPageExample() {
// 模拟API调用
const fetchPage = (page, size) => {
const allData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const start = page * size;
const end = start + size;
const items = allData.slice(start, end);
return Promise.resolve({ items });
};
const iterator = createPaginationIterator(fetchPage, 3);
let result = await iterator.next();
while (!result.done) {
console.log(result.value);
result = await iterator.next();
}
}
2. 自定义树结构遍历
迭代器非常适合遍历树状结构,例如DOM树或文件系统:
function createTreeIterator(root) {
// 使用深度优先搜索算法
const stack = [root];
return {
next() {
if (stack.length === 0) {
return { done: true };
}
const node = stack.pop();
// 将子节点压入栈中(逆序,以便正确的遍历顺序)
if (node.children && node.children.length > 0) {
for (let i = node.children.length - 1; i >= 0; i--) {
stack.push(node.children[i]);
}
}
return {
value: node,
done: false
};
}
};
}
// 使用示例
const fileSystem = {
name: 'root',
children: [
{
name: 'documents',
children: [
{ name: 'resume.pdf' },
{ name: 'notes.txt' }
]
},
{
name: 'pictures',
children: [
{ name: 'vacation.jpg' },
{ name: 'family.jpg' }
]
}
]
};
const iterator = createTreeIterator(fileSystem);
let result = iterator.next();
while (!result.done) {
console.log(result.value.name);
result = iterator.next();
}
// 输出:
// root
// documents
// resume.pdf
// notes.txt
// pictures
// vacation.jpg
// family.jpg
生成器(Generator)与迭代器
生成器(Generator)是ES6引入的另一个特性,它提供了一种更简单的方式来创建迭代器。生成器是一种特殊的函数,可以在执行过程中暂停和恢复。
生成器函数使用function*
语法定义,并使用yield
关键字输出值。每次调用生成器的next()
方法时,函数会执行到下一个yield
语句。
下面是一个简单的生成器示例:
function* simpleGenerator() {
yield 1;
yield 2;
yield 3;
}
const gen = simpleGenerator();
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 }
生成器可以大大简化迭代器的创建。我们可以使用生成器重写前面的数组迭代器示例:
function* createArrayIterator(array) {
for (let i = 0; i < array.length; i++) {
yield array[i];
}
}
const myArray = [1, 2, 3, 4];
const iterator = createArrayIterator(myArray);
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: 4, done: false }
console.log(iterator.next()); // 输出: { value: undefined, done: true }
无限迭代器
迭代器的一个有趣应用是创建无限序列。例如,我们可以创建一个生成斐波那契数列的无限迭代器:
function* fibonacciGenerator() {
let a = 0, b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacciGenerator();
// 获取前10个斐波那契数
for (let i = 0; i < 10; i++) {
console.log(fib.next().value);
}
// 输出: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
在上面的例子中,fibonacciGenerator
生成器可以无限生成斐波那契数列。由于它是一个无限序列,我们只取了前10个数。
使用无限迭代器时要小心,确保有终止条件,否则可能导致无限循环。
总结
迭代器是JavaScript中一个强大的特性,它为我们提供了一种统一的方式来遍历各种数据集合。通过实现next()
方法,迭代器使我们能够按顺序访问集合中的元素,而无需了解集合的内部结构。
JavaScript中的许多内置对象都是可迭代的,如数组、字符串、Map和Set等。此外,我们还可以创建自己的迭代器和可迭代对象,以满足特定的需求。
生成器(Generator)进一步简化了迭代器的创建过程,使我们能够以更简洁、更直观的方式定义迭代逻辑。
迭代器在处理大量数据、异步操作、树状结构遍历等场景中有着广泛的应用。掌握迭代器的概念和用法,将帮助你写出更简洁、更高效的JavaScript代码。
练习与资源
练习
- 创建一个迭代器,可以遍历一个对象的所有属性和值。
- 实现一个"范围迭代器",可以生成指定范围内的所有整数。
- 使用生成器创建一个迭代器,可以按层次遍历一个嵌套数组(例如:
[1, [2, 3], [4, [5, 6]]]
)。
进一步学习的资源
- MDN Web Docs: Iteration protocols
- MDN Web Docs: Generator
- JavaScript.info: Iterables
- Exploring JS: Iterables and iterators
通过深入学习和实践,你将能够充分利用JavaScript迭代器这一强大特性,编写更加优雅和高效的代码。