跳到主要内容

JavaScript 迭代器

什么是迭代器?

迭代器是JavaScript中一种特殊对象,它提供了一种机制,让我们能够按顺序访问集合中的元素,而无需了解集合的内部结构。迭代器对象通过一个next()方法实现,每次调用这个方法都会返回集合中的下一个元素。

备注

迭代器是ES6(ECMAScript 2015)引入的新特性,它让我们能够以统一的方式遍历不同类型的数据集合。

迭代器的基本原理

一个迭代器对象必须实现next()方法,该方法返回一个包含两个属性的对象:

  • value: 当前元素的值
  • done: 一个布尔值,表示是否已经遍历到集合的末尾

donetrue时,表示迭代已经完成。

创建一个简单的迭代器

下面是一个简单的迭代器示例,它可以遍历一个数组:

javascript
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对象

创建一个可迭代对象

下面是一个自定义可迭代对象的示例:

javascript
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循环是遍历可迭代对象最简单的方式:

javascript
const numbers = [1, 2, 3, 4, 5];

for (const num of numbers) {
console.log(num); // 依次输出: 1, 2, 3, 4, 5
}

2. 展开运算符(Spread Operator)

展开运算符(...)可以将可迭代对象展开成独立的元素:

javascript
const parts = ['shoulders', 'knees'];
const body = ['head', ...parts, 'and', 'toes'];

console.log(body); // 输出: ['head', 'shoulders', 'knees', 'and', 'toes']

3. 解构赋值

解构赋值可以从可迭代对象中提取值:

javascript
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()方法可以从可迭代对象创建一个新数组:

javascript
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,每次只返回一页数据。我们可以创建一个迭代器来轻松遍历所有页的数据:

javascript
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树或文件系统:

javascript
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语句。

下面是一个简单的生成器示例:

javascript
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 }

生成器可以大大简化迭代器的创建。我们可以使用生成器重写前面的数组迭代器示例:

javascript
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 }

无限迭代器

迭代器的一个有趣应用是创建无限序列。例如,我们可以创建一个生成斐波那契数列的无限迭代器:

javascript
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. 使用生成器创建一个迭代器,可以按层次遍历一个嵌套数组(例如:[1, [2, 3], [4, [5, 6]]])。

进一步学习的资源

通过深入学习和实践,你将能够充分利用JavaScript迭代器这一强大特性,编写更加优雅和高效的代码。