ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

30 seconds of code:JavaScript 中 for...in、for...of 与 forEach() 三种迭代方式的完整对比

2026/10/4 9:49:27 拓冰建站 浏览量
30 seconds of code:JavaScript 中 for...in、for...of 与 forEach() 三种迭代方式的完整对比 教程文档【免费下载链接】30-seconds-of-codeCoding articles to level up your development skills项目地址https://gitcode.com/gh_mirrors/30/30-seconds-of-code点击查看免费下载遍历数组、对象、字符串乃至Map、Set是 JavaScript 开发中最高频的操作之一但for...in、for...of与Array.prototype.forEach()三者到底有何区别却常常让初学者甚至资深开发者混淆。本篇基于 30 seconds of code 仓库中的核心文档 for-in-for-of-foreach系统梳理三种迭代方式的适用对象、取值语义、边界陷阱并结合作者仓库内 array-map-vs-foreach、for-each-right、for-loop-early-break、async-array-loops 等关联片段给出每个场景下最正确的选型结论。读完本文你将能一眼判断任意数据结构该用哪种迭代方式并避开原型链污染、TypeError: not iterable、回调无法提前退出等常见坑。for...in遍历可枚举属性含继承属性for...in用于遍历一个对象的所有可枚举属性包括继承自原型链的可枚举属性。它适用于数组、字符串和普通对象但不能用于Map或Set。看文档中的四个例子for (let prop in [a, b, c]) console.log(prop); // 0, 1, 2 (数组索引) for (let prop in str) console.log(prop); // 0, 1, 2 (字符串索引) for (let prop in {a: 1, b: 2, c: 3}) console.log(prop); // a, b, c (对象属性名) for (let prop in new Set([a, b, a, d])) console.log(prop); // undefined (没有可枚举属性)关键点在于for...in取出的是键key而不是值数组上取出的是索引下标字符串上取出的是字符下标普通对象上取出的是属性名。而对Set来说它内部并没有暴露可枚举属性因此循环体内什么都拿不到。警惕原型链上的可枚举属性for...in会沿原型链向上遍历所有可枚举属性这通常是我们并不想要的行为。仓库中的另一篇文章 eslint-refactor-for-in 开篇就引出了 ESLint 对它的经典警告for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.eslint(no-restricted-syntax)如果你确实想只遍历对象自身的键需要配合Object.prototype.hasOwnProperty()做过滤而更推荐的做法是直接用Object.keys()、Object.values()、Object.entries()把键/值/键值对先取成数组再交给数组方法处理。该文给出的三个替代方案如下const data [3, 4]; // 与 for (let k in data) console.log(k) 行为完全一致 Object.keys(data).forEach(k console.log(k)); // 0 1 // 只想拿值时用 Object.values() Object.values(data).forEach(v console.log(v)); // 3 4 // 键和值都要时用 Object.entries() Object.entries(data).forEach(e console.log(e[0], e[1])); // 0 3, 1 4这三个静态方法都只返回对象自身的键/值/键值对天然绕开了原型链问题是重构for...in最稳妥的手段。for...of遍历可迭代对象的值for...of用于遍历可迭代iterable对象取出的是元素的值而不是属性。它适用于数组、字符串、Map、Set但不能用于普通对象——因为普通对象默认没有实现迭代器协议。for (let val of [a, b, c]) console.log(val); // a, b, c (数组元素值) for (let val of str) console.log(val); // s, t, r (字符串字符) for (let val of {a: 1, b: 2, c: 3}) console.log(val); // TypeError: obj is not iterable for (let val of new Set([a, b, a, d])) console.log(val); // a, b, d (Set 去重后的值)注意上面普通对象的例子会直接抛出TypeError: not iterable因为for...of依赖目标对象的Symbol.iterator方法而普通对象并没有实现它。同时这个例子也顺带展示了Set的一个特性元素自动去重[a, b, a, d]迭代出来只有a, b, d。想要索引用Array.prototype.entries()for...of默认只能拿到值这常被拿来与forEach()的「值 索引」能力做对比。但仓库中的 index-for-of-loop 指出这其实不是限制——结合Array.prototype.entries()即可同时拿到索引和值const items [a, b, c]; for (let [index, item] of items.entries()) { console.log(${index}: ${item}); } // LOGS: 0: a, 1: b, 2: c字符串可以先用展开运算符...转成数组再如法炮制Map.prototype.entries()和Set.prototype.entries()也提供了完全相同的用法。与 async/await 的天然亲和力for...of与for循环在异步场景下还有一个显著优势循环体内可以使用await且所有 Promise 会按顺序依次执行。仓库中的 async-array-loops 给出了对照const asyncUppercase item new Promise(resolve setTimeout(() resolve(item.toUpperCase()), Math.floor(Math.random() * 1000)) ); const uppercaseItems async () { const items [a, b, c]; for (item of items) { const uppercaseItem await asyncUppercase(item); console.log(uppercaseItem); } console.log(Items processed); }; // Logs: A, B, C, Items processed (严格按顺序)如果你需要并行执行则改用Promise.all()Array.prototype.map()但千万不要把async回调直接传给forEach()因为它不会等待 Promise 完成代码会继续往下跑导致「Items processed」先于异步结果输出。Array.prototype.forEach()数组元素的专用方法forEach()是Array原型上的方法只能作用于数组以及类数组对象但它在遍历时可以同时访问元素的值和索引[a, b, c].forEach( val console.log(val) // a, b, c (数组元素值) ); [a, b, c].forEach( (val, i) console.log(i) // 0, 1, 2 (数组索引) );回调函数还接收可选的第三个参数——数组本身这在需要内部引用原数组如合并相邻元素时很有用。返回 undefined与 map() 的本质区别forEach()的返回值恒为undefined它只负责「对每个元素执行副作用」不产生新数组。这与Array.prototype.map()形成鲜明对照——map()返回一个由回调结果构成的新数组代表「转换」。仓库中的 array-map-vs-foreach 用一个例子把二者区分得清清楚楚const numbers [1, 2, 3, 4, 5]; numbers.forEach(num console.log(num * 2)); // 无返回值仅输出: 2, 4, 6, 8, 10 const doubledNumbers numbers.map(num num * 2); // 返回新数组: [2, 4, 6, 8, 10]一句话记忆法map()是转换transformationforEach()是遍历iteration。当你只是想对每个元素执行副作用打印、写入、发请求时用forEach()当你想基于原数组生成一个新数组时务必用map()。两个不能忽略的短板不能 break、不能 awaitforEach()的第一个短板是无法提前跳出循环——break、continue、return都无法终止它。仓库中的 for-loop-early-break 展示了一个典型场景判断两个数组是否相等用forEach()即使已经发现不相等也会把largeArray全部遍历完const smallArray [0, 2]; const largeArray Array.from({ length: 1000 }, (_, i) i); const areEqual (a, b) { let result true; a.forEach((x, i) { if (!result) return; if (b[i] undefined || x ! b[i]) result false; }); return result; }; areEqual(largeArray, smallArray); // false // 但会白白遍历 largeArray 的全部 1000 项而换成传统for循环加提前return一遇到第一个不匹配项就立刻退出const areEqual (a, b) { for (let i in a) { if (b[i] undefined || a[i] ! b[i]) return false; } return true; }; areEqual(largeArray, smallArray); // false // 只会遍历到第一个不匹配的位置就返回第二个短板在前文已提async回调传入forEach()后外层await并不会等待所有异步操作完成输出顺序与预期完全不可控见 async-array-loops 中的第三种写法。反向遍历slice reverse forEachforEach()默认从左到右遍历。若想从右向左仓库中的 for-each-right 给出了一个优雅且不污染原数组的组合拳——先slice()浅拷贝再reverse()最后forEach()const forEachRight (arr, callback) arr.slice().reverse().forEach(callback); forEachRight([1, 2, 3, 4], val console.log(val)); // 4, 3, 2, 1三分钟选型速查表迭代方式取出的是什么适用对象不适用对象能否提前退出是否支持 await 顺序执行for...in键含继承的可枚举属性数组、字符串、普通对象Map、Set可以break/return可以for...of值数组、字符串、Map、Set普通对象非 iterable可以break/return可以Array.prototype.forEach()值 索引 数组本身数组普通对象、Map、Set不可以不可以异步会乱序/不等补充两条实战经验遍历普通对象的键/值优先Object.keys()/Object.values()/Object.entries() 数组方法彻底避开for...in的原型链遍历问题参考 eslint-refactor-for-in需要索引的for...of用items.entries()解构[index, item]兼顾可读性与break能力参考 index-for-of-loop需要提前退出或按序 await 的场景直接用for/for...of这是数组方法给不了的能力参考 for-loop-early-break 与 async-array-loops。相关阅读Array.prototype.map() 与 Array.prototype.forEach() 的区别返回值决定两者不可互换在 for...of 循环中获取数组索引entries()的妙用从右向左遍历数组slice().reverse().forEach()组合需要提前退出时使用 for 循环forEach()无法 break 的解决方案JavaScript 异步数组循环for...of顺序执行 vsPromise.all()并行执行重构 for...in 以规避 ESLint 警告Object.keys/values/entries替代方案上述片段统一收录于 JavaScript Array Methods 与 JavaScript Interview Questions 两个集合中前者汇总了数组遍历与变换的常用技巧后者则把forEach与map的对比列为高频面试题之一赞分享教程文档【免费下载链接】30-seconds-of-codeCoding articles to level up your development skills项目地址https://gitcode.com/gh_mirrors/30/30-seconds-of-code点击查看免费下载相关推荐30 seconds of codeJavaScript 数组比较的完整实战指南30 seconds of codeJavaScript 数组比较的完整实战指南 导读 在 JavaScript 中比较两个数组是否相等是开发中最高频也教程文档30-seconds-of-code重构 for...in 循环以消除 ESLint 警告的三种方案30 seconds of code重构 for...in 循环以消除 ESLint 警告的三种方案 ESLint 是 JavaScript 开发中最常用的静教程文档30 seconds of codeJavaScript 异步数组循环的三种方案与避坑指南30 seconds of codeJavaScript 异步数组循环的三种方案与避坑指南 异步操作与数组循环的组合是 JavaScript 开发中最容易踩坑教程文档创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考