JavaScript 可迭代数据类型详解:掌握 for…of 的奥秘
在 JavaScript 中,可迭代对象是指实现了 可迭代协议 的对象,它们可以使用 for...of
循环进行遍历。理解哪些数据类型是可迭代的,对于编写高效、现代的 JavaScript 代码至关重要。
什么是可迭代对象?
可迭代对象必须实现 Symbol.iterator
方法,该方法返回一个迭代器对象。迭代器对象有一个 next()
方法,每次调用返回一个包含 value
和 done
属性的对象。
const iterable = {
{
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 内置的可迭代数据类型
1. Array(数组)✅
数组是最常见的可迭代对象:
const fruits = ['🍎', '🍌', '🍊'];
for (const fruit of fruits) {
console.log(fruit); // 🍎, 🍌, 🍊
}
2. String(字符串)✅
字符串也是可迭代的,会逐个返回字符:
const greeting = 'Hello';
for (const char of greeting) {
console.log(char); // H, e, l, l, o
}
3. Map ✅
Map 对象在迭代时会返回 [key, value]
对:
const userMap = new Map([
['name', 'Alice'],
['age', 30]
]);
for (const [key, value] of userMap) {
console.log(`${key}: ${value}`);
}
// 输出:
// name: Alice
// age: 30
4. Set ✅
Set 对象迭代时返回集合中的每个值:
const uniqueNumbers = new Set([1, 2, 2, 3]);
for (const num of uniqueNumbers) {
console.log(num); // 1, 2, 3
}
5. TypedArray(类型化数组)✅
如 Int8Array, Uint8Array 等:
const intArray = new Int8Array([10, 20, 30]);
for (const num of intArray) {
console.log(num); // 10, 20, 30
}
6. Arguments 对象 ✅
函数内部的 arguments 对象是可迭代的:
function sum() {
let total = 0;
for (const arg of arguments) {
total += arg;
}
return total;
}
console.log(sum(1, 2, 3)); // 6
7. DOM 集合 ✅
许多 DOM 集合是可迭代的:
// 获取所有 div 元素
const divs = document.querySelectorAll('div');
// 使用 for...of 遍历
for (const div of divs) {
console.log(div);
}
8. Generator 对象 ✅
生成器函数返回的对象是可迭代的:
function* generateSequence() {
yield 1;
yield 2;
yield 3;
}
for (const value of generateSequence()) {
console.log(value); // 1, 2, 3
}
不可迭代的数据类型
1. Object(普通对象)❌
普通对象默认不可迭代:
const person = { name: 'Alice', age: 30 };
// 会报错: person is not iterable
for (const key of person) {
console.log(key);
}
2. WeakMap 和 WeakSet ❌
这些弱引用集合不可迭代:
const weakMap = new WeakMap();
weakMap.set({}, 'value');
// 报错: weakMap is not iterable
for (const entry of weakMap) {
console.log(entry);
}
如何使普通对象可迭代
虽然普通对象默认不可迭代,但我们可以使其可迭代:
const person = {
name: 'Alice',
age: 30,
job: 'Developer',
{
const keys = Object.keys(this);
let index = 0;
return {
next: () => {
if (index < keys.length) {
const key = keys[index++];
return { value: [key, this[key]], done: false };
}
return { done: true };
}
};
}
};
for (const [key, value] of person) {
console.log(`${key}: ${value}`);
}
// 输出:
// name: Alice
// age: 30
// job: Developer
for…of 与 for…in 的区别
特性 | for…of | for…in |
---|---|---|
用途 | 遍历可迭代对象的值 | 遍历对象的可枚举属性 |
返回值 | 值 | 键/属性名 |
原型链 | 不遍历原型链 | 遍历原型链上的可枚举属性 |
适用对象 | 可迭代对象 | 所有对象 |
数组遍历 | 值 | 索引 |
顺序 | 保持插入顺序 | 不保证顺序(虽然通常按插入顺序) |
实际应用场景
1. 遍历 Map 的键值对
const settings = new Map([
['theme', 'dark'],
['fontSize', 16],
['notifications', true]
]);
for (const [key, value] of settings) {
console.log(`Setting ${key} is set to ${value}`);
}
2. 处理字符串中的字符
function countVowels(str) {
const vowels = 'aeiouAEIOU';
let count = 0;
for (const char of str) {
if (vowels.includes(char)) count++;
}
return count;
}
console.log(countVowels('Hello World')); // 3
3. 自定义数据结构的迭代
class Range {
constructor(start, end) {
this.start = start;
this.end = end;
}
* {
for (let i = this.start; i <= this.end; i++) {
yield i;
}
}
}
const range = new Range(1, 5);
for (const num of range) {
console.log(num); // 1, 2, 3, 4, 5
}
4. 异步迭代
ES2018 引入了异步迭代器,可以与 for await...of
一起使用:
async function fetchUrls(urls) {
for await (const response of fetchMultiple(urls)) {
console.log(await response.text());
}
}
async function* fetchMultiple(urls) {
for (const url of urls) {
yield fetch(url);
}
}
常见问题解答
Q: 如何判断一个对象是否可迭代?
function isIterable(obj) {
return obj != null && typeof obj[Symbol.iterator] === 'function';
}
console.log(isIterable([])); // true
console.log(isIterable({})); // false
Q: 为什么普通对象不可迭代?
- 历史原因:ES6 之前没有迭代协议
- 设计选择:对象属性的迭代顺序不确定
- 替代方案:使用
Object.keys()
,Object.values()
,Object.entries()
Q: 如何遍历对象的键值对?
const person = { name: 'Alice', age: 30 };
// 使用 Object.entries 转换为可迭代的键值对数组
for (const [key, value] of Object.entries(person)) {
console.log(`${key}: ${value}`);
}
Q: for…of 可以用于 NodeList 吗?
是的,NodeList
是可迭代的:
const buttons = document.querySelectorAll('button');
for (const button of buttons) {
button.addEventListener('click', handleClick);
}
总结
JavaScript 中可迭代的数据类型包括:
Array
String
Map
Set
TypedArray
Arguments
对象DOM
集合Generator
对象
不可迭代的数据类型包括:
- 普通
Object
WeakMap
WeakSet
掌握这些可迭代类型及其使用场景,可以让你:
- 编写更简洁、更现代的代码
- 更好地处理集合数据
- 创建自定义的可迭代数据结构
- 利用迭代协议实现更高级的功能
for...of
循环是处理可迭代对象的强大工具,它提供了比传统 for
循环更简洁、更安全的语法,同时避免了 for...in
循环的一些陷阱。