在 JavaScript 中,for...of
循环是遍历可迭代对象(如数组、字符串、Set、Map 等)的常用语法,相比 for...in
更专注于值的遍历。但在使用时仍需注意以下细节,以避免潜在问题:
一、仅遍历可迭代对象,非可迭代需转换
注意场景
for...of
只能遍历实现了Symbol.iterator
接口的可迭代对象,普通对象(Object)默认不可迭代。- 类数组对象(如
arguments
、DOM 节点列表)需先转换为数组才能使用for...of
。
示例与解决方案
// 错误示例:直接遍历普通对象
const obj = {
name: '豆包', age: 20 };
for (const value of obj) {
// 报错:Uncaught TypeError: obj is not iterable
}
// 正确做法1:对象转可迭代结构(如Object.entries())
for (const [key, value] of Object.entries(obj)) {
console.log(key, value); // 输出: "name" "豆包", "age" 20
}
// 错误示例:遍历类数组对象(arguments)
function demo() {
for (const item of arguments) {
// 若浏览器环境不支持,可能报错(需ES6+支持)
}
}
// 正确做法2:类数组转数组(Array.from或展开运算符)
function demo() {
for (const item of Array.from(arguments)) {
console.log(item);
}
}
demo(1, 2, 3);
AI 代码解读
二、遍历数组时无法直接获取索引
注意场景
for...of
直接获取数组元素值,若需索引需额外处理,否则可能导致逻辑错误。
解决方案
- 结合 entries() 方法:
const fruits = ['苹果', '香蕉', '橙子']; for (const [index, fruit] of fruits.entries()) { console.log(index, fruit); // 输出: 0 "苹果", 1 "香蕉", 2 "橙子" }
AI 代码解读 - 手动维护索引变量:
let index = 0; for (const fruit of fruits) { console.log(index++, fruit); // 同上效果 }
AI 代码解读
三、跳过空元素(hole)的处理
注意场景
数组中的空元素(通过 delete
删除或直接留空)会被 for...of
跳过,与 forEach
和普通 for
循环行为不同。
const arr = [1, , 3]; // 中间为空元素
console.log(arr.length); // 输出: 3
// for...of 遍历效果
for (const item of arr) {
console.log(item); // 输出: 1, 3(跳过空元素)
}
// forEach 遍历效果
arr.forEach(item => {
console.log(item); // 输出: 1, undefined, 3(空元素转为undefined)
});
// 普通for循环效果
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]); // 输出: 1, undefined, 3(同上)
}
AI 代码解读
解决方案
若需处理空元素,可先通过 fill()
填充或转换为密集数组:
const filledArr = arr.fill(0); // 空元素转为0
for (const item of filledArr) {
console.log(item); // 输出: 1, 0, 3
}
AI 代码解读
四、无法中断遍历的场景限制
注意场景
for...of
支持使用 break
、continue
、return
控制流程,但无法像 forEach
一样通过 try/catch
以外的方式中断异步操作(如 Promise)。
// 正确示例:使用break中断同步遍历
for (const num of [1, 2, 3, 4]) {
if (num === 3) break;
console.log(num); // 输出: 1, 2
}
// 异步场景限制:无法直接中断Promise链
async function demo() {
for (const i of [1, 2, 3]) {
await new Promise(resolve => setTimeout(resolve, 100));
if (i === 2) break; // 虽break,但已发出的Promise仍会执行
console.log(i); // 输出: 1, 2(i=2时break,但第2次Promise仍完成)
}
}
AI 代码解读
解决方案
异步场景中如需严格中断,可改用 for
循环配合 try/catch
或标志位:
async function demo() {
let shouldBreak = false;
for (let i = 0; i < 3 && !shouldBreak; i++) {
try {
await new Promise(resolve => setTimeout(resolve, 100));
if (i === 2) {
shouldBreak = true;
continue;
}
console.log(i); // 仅输出: 1(i=2时标记break,不再继续循环)
} catch (e) {
// 处理异常
}
}
}
AI 代码解读
五、与解构赋值结合的注意事项
注意场景
for...of
支持解构赋值,但需注意结构匹配,否则可能得到 undefined
。
const users = [
{
name: '张三', age: 18 },
{
name: '李四', age: 20 }
];
// 正确解构:获取对象属性
for (const {
name, age } of users) {
console.log(name, age); // 正常输出
}
// 错误解构:属性名不匹配
for (const {
username, age } of users) {
console.log(username); // 输出: undefined, undefined(users无username属性)
}
AI 代码解读
解决方案
解构时确保属性名与实际对象一致,或使用默认值避免 undefined
:
for (const {
name = '匿名', age } of users) {
console.log(name, age); // 若对象无name属性,默认显示"匿名"
}
AI 代码解读
六、遍历迭代器时的状态管理
注意场景
手动使用迭代器(如 arr[Symbol.iterator]()
)时,for...of
会自动管理迭代状态,但若中途修改原数组,可能影响遍历结果。
const arr = [1, 2, 3];
const iterator = arr[Symbol.iterator](); // 获取迭代器
// 第一次遍历:正常输出1,2,3
for (const item of iterator) {
console.log(item);
}
// 第二次遍历:迭代器已耗尽,无输出
for (const item of iterator) {
console.log(item); // 无输出
}
// 错误示例:遍历中修改数组长度
const arr2 = [1, 2, 3];
for (const item of arr2) {
arr2.push(4); // 遍历中添加元素,部分浏览器可能报错或跳过新元素
console.log(item); // 输出: 1, 2, 3(新元素4可能不被遍历)
}
AI 代码解读
解决方案
- 迭代器仅可遍历一次,若需重复遍历需重新获取迭代器。
- 遍历过程中避免修改原数组长度,否则可能导致不可预测的结果。
七、总结:最佳实践与注意要点
- 明确可迭代对象:先确认目标是否为可迭代对象,普通对象需转换为
Object.entries()
等结构。 - 索引需求处理:通过
entries()
或手动维护索引,避免直接依赖for...of
的值遍历。 - 空元素处理:若数组包含空元素(hole),需注意
for...of
会跳过,按需填充或转换数组。 - 异步场景限制:
for...of
无法中断已发出的异步操作,需配合标志位或其他控制逻辑。 - 解构赋值规范:确保解构结构与数据匹配,必要时设置默认值。
遵循以上注意事项,可充分发挥 for...of
循环在遍历可迭代对象时的简洁性与可靠性,避免因语法特性导致的逻辑错误。