别再混用 for...in 和 for...of 了!前端老鸟都踩过的坑全解析
- 别再混用 for...in 和 for...of 了!前端老鸟都踩过的坑全解析
- 先上结论,背不下来就抄桌面
- 血统普查:for...in 到底是个啥?
- for...of 的自我介绍:我只跟“可迭代”玩
- 数组遍历的四种姿势现场 PK
- 对象遍历的正确打开方式
- 类数组怪咖:NodeList 与 arguments
- 真实踩坑三连:我血淋淋的教训
- 坑一:for...in 渲染列表,页面多出 N 个“undefined”
- 坑二:for...of 中途想 break,结果写了一堆 return
- 坑三:给对象扩展方法,导致 for...in 把方法也序列化
- 手写一个“无脑遍历”小工具
- 面试必杀:for...in vs for...of vs forEach vs map
- 兼容性彩蛋:IE11 的倔强
- 结语:把口诀抄墙上,别再踩坑了
别再混用 for…in 和 for…of 了!前端老鸟都踩过的坑全解析
“哥,我页面咋卡死了?”
“你 for…in 了个数组?”
“……嗯。”
“把电脑关了吧,去楼下跑十圈,冷静一下。”
上面这段对话,发生在我刚转行前端的第三周。那天我自信满满地写了段“遍历数组”的代码,结果把 Array.prototype 上的所有方法连带用户扩展的插件一口气全跑了一遍,浏览器直接原地升天。老板路过时,我屏幕还卡在 99% CPU,风扇嗡嗡作响,像要起飞。那一刻,我深刻体会到:for…in 和 for…of,这俩孪生兄弟,一个是亲生的,一个是捡来的,用错一次,社死一年。
今天咱们就把这俩货摁在地板上摩擦,从血统到脾气,从性能到面试,一次性讲干净。顺带送你一堆“一看就能抄”的代码片段,以后谁再问你“遍历怎么写”,直接把文章甩过去,让他自己悟。
先上结论,背不下来就抄桌面
in 遍历键,of 遍历值;
对象用 in,可迭代用 of;
数组别用 in,除非你想看 prototype 跳大神;
对象想用 of?先给它整一个 Symbol.iterator,不然就报错给你看。
背完口诀,咱们开始拆招。
血统普查:for…in 到底是个啥?
for…in 的设计初衷就是“扫遍对象所有可枚举属性”,注意关键词:对象、可枚举、属性。
换句话说,它天生就是给{}用的,数组只是顺带,而且顺带得特别敷衍——把数组当下标对象跑一遍,顺便把原型链上的“七大姑八大姨”全请出来。
// demo:Array.prototype 被污染的场景Array.prototype.foo=()=>{};constarr=['a','b','c'];for(constkeyinarr){console.log(key,arr[key]);}// 0 'a'// 1 'b'// 2 'c'// foo () => {} ← 惊喜不惊喜?看到没?下标 0 1 2 还算正常,突然蹦出来一个foo,当场社死。
所以社区才有 ESLint 规则no-for-in-array,这不是“社区事儿多”,是社区被你坑怕了。
for…of 的自我介绍:我只跟“可迭代”玩
for…of 是 ES6 新贵,眼里只有“可迭代协议”。
啥叫可迭代?就是实现了Symbol.iterator的东西:数组、字符串、Map、Set、NodeList、arguments、生成器……
普通对象?没有默认 iterator,直接报错:
constobj={x:1,y:2};for(constvalueofobj){console.log(value);}// TypeError: obj is not iterable报错信息干净利落,不跟你多 BB。
想让它可迭代?行,手写一个:
// 给对象挂个 iterator,立刻翻身做主人obj[Symbol.iterator]=function*(){for(constkeyofObject.keys(this)){yieldthis[key];// 只吐值}};// 现在可以 of 了for(constvalueofobj){console.log(value);// 1 2}上面这段代码,面试里经常让你手写,记住yield别写成return,否则迭代一次就熄火。
数组遍历的四种姿势现场 PK
直接上跑道,分别跑 100 万个数字,看谁快:
constlen=1_000_000;constarr=Array.from({length:len},(_,i)=>i);console.time('for-i');for(leti=0;i<arr.length;i++){// 空跑}console.timeEnd('for-i');console.time('for-of');for(constvofarr){// 空跑}console.timeEnd('for-of');console.time('forEach');arr.forEach(()=>{});console.timeEnd('forEach');console.time('for-in');for(constkinarr){// 空跑}console.timeEnd('for-in');Chrome 121 本地实测(Mac M1):
for-i : 4.8 ms for-of : 7.2 ms forEach : 9.5 ms for-in : 120 ms ← 慢出天际,还附赠 prototype 垃圾结论:
- 传统
for(let i=0)依旧最快,但写法丑; for...of牺牲一点点性能换可读性,值得;forEach慢在回调开销,跑大数据量时差距明显;for...in直接抬走,下一个。
对象遍历的正确打开方式
场景:接口返回一个字典,你要把 value 累加,又怕原型链污染。
// 安全起见,三件套带走:hasOwnProperty + Object.keys + for...ofconstdict=Object.create(null);// 没有原型,干净dict.a=1;dict.b=2;letsum=0;for(constkeyofObject.keys(dict)){sum+=dict[key];}console.log(sum);// 3如果你非要用 for…in,那就加保险:
for(constkeyindict){if(Object.prototype.hasOwnProperty.call(dict,key)){sum+=dict[key];}}注意别直接dict.hasOwnProperty,因为对象本身可能把hasOwnProperty当字段名重写了,别笑,真有这种骚操作。
类数组怪咖:NodeList 与 arguments
DOM API 返回的NodeList早期是“类数组”,它自带迭代器,所以:
// 现代浏览器,直接 for...offor(constnodeofdocument.querySelectorAll('div')){node.classList.add('highlight');}但在远古浏览器(IE11 及更老)里,没有迭代器,只能:
// 复古写法constdivs=document.querySelectorAll('div');for(leti=0;i<divs.length;i++){divs[i].classList.add('highlight');}或者 Array 化:
// slice 一刀,瞬间变真数组constdivArr=Array.prototype.slice.call(divs);divArr.forEach(node=>node.classList.add('highlight'));arguments同理,现代项目直接 rest 参数替代:
// 旧functionoldAdd(){letsum=0;for(leti=0;i<arguments.length;i++){sum+=arguments[i];}returnsum;}// 新constnewAdd=(...nums)=>{letsum=0;for(constnofnums)sum+=n;returnsum;};真实踩坑三连:我血淋淋的教训
坑一:for…in 渲染列表,页面多出 N 个“undefined”
当年用 Vue2,写了个v-for="item in obj",结果obj是数组,我却在 methods 里for (const key in obj)去累加,返回了一个数组——模板里直接渲染出undefined undefined undefined。
调试半小时,才发现key是字符串索引,累加的时候obj['0']能取到值,但拼回去的下标错位,简直怀疑人生。
坑二:for…of 中途想 break,结果写了一堆 return
forEach里不能break,这是常识;for...of可以break,却有人不知道:
// 找到第一个负数就停for(constnofnums){if(n<0){console.log('found',n);break;// OK}}我当年在forEach里写return想跳出,结果只跳了当前回调,循环继续跑,硬生生把 10 万条数据全跑完,用户电脑风扇直接起飞。
坑三:给对象扩展方法,导致 for…in 把方法也序列化
后端要的 JSON 里突然多出一个serialize函数,一查,原来我在原型上挂了工具方法:
Object.prototype.serialize=function(){returnJSON.stringify(this);};然后for...in把方法名也扫进去,后端直接 400。
解决:要么别挂原型,要么序列化前用Object.keys过滤。
手写一个“无脑遍历”小工具
要兼容对象、数组、类数组、甚至 Map/Set,可以整一个万能函数:
/** * 安全遍历任何可迭代/类对象 * @param {any} target * @param {(value: any, key: any) => void} callback * @param {boolean} [earlyExit=false] 是否允许 callback 返回 true 时提前退出 */functioneach(target,callback,earlyExit=false){if(target==null)return;// 数组、字符串、Map、Set 等可迭代if(typeoftarget[Symbol.iterator]==='function'){if(earlyExit){for(const[key,value]oftarget.entries()){if(callback(value,key)===true)break;}}else{for(const[key,value]oftarget.entries()){callback(value,key);}}return;}// 普通对象if(earlyExit){for(constkeyofObject.keys(target)){if(callback(target[key],key)===true)break;}}else{for(constkeyofObject.keys(target)){callback(target[key],key);}}}// 用法示例each({x:1,y:2},console.log);// 1 'x'// 2 'y'each(['a','b'],console.log);// 'a' 0// 'b' 1// Mapconstmap=newMap([['k1','v1'],['k2','v2']]);each(map,(v,k)=>console.log(k,v));// k1 v1// k2 v2上面这段代码,你可以直接塞进项目 utils,再也不用纠结“我到底该用哪个循环”。
面试必杀:for…in vs for…of vs forEach vs map
一句话版本:
for...in:遍历对象可枚举键,会爬原型,数组慎用;for...of:遍历可迭代结构的值,支持break/continue/return,最现代;forEach:数组专属回调,不能break,返回值undefined,函数式风味;map:数组回调,返回新数组,不修改原数组,适合纯函数转换。
再背一个口诀:
“in 键,of 值;Each 走一圈,Map 换新颜。”
兼容性彩蛋:IE11 的倔强
虽然 2026 年了,IE11 基本入土,但有些政企项目(懂的都懂)还在跑。for...of在 IE11 里语法直接报错,必须上core-js或者 Babel 插件转译。
如果你不想打包体积飙车,那就乖乖:
// 政企兼容写法constkeys=Object.keys(obj);for(leti=0;i<keys.length;i++){constvalue=obj[keys[i]];// ...}结语:把口诀抄墙上,别再踩坑了
in 遍历键,of 遍历值;
对象用 in,可迭代用 of;
数组别用 in,除非你想看 prototype 跳大神;
对象想用 of?先给它整一个 Symbol.iterator,不然就报错给你看。
背下来,以后写循环,三秒出答案。
要是再写错——就把这篇文章打印出来,贴显示器边,每天朗读三遍,直到梦里都能默写。
祝你以后遍历顺风顺水,早日升职加薪,不再被风扇嘲笑。
欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
推荐:DTcode7的博客首页。
一个做过前端开发的产品经理,经历过睿智产品的折磨导致脱发之后,励志要翻身农奴把歌唱,一边打入敌人内部一边持续提升自己,为我们广大开发同胞谋福祉,坚决抵制睿智产品折磨我们码农兄弟!
| 专栏系列(点击解锁) | 学习路线(点击解锁) | 知识定位 |
|---|---|---|
| 《微信小程序相关博客》 | 持续更新中~ | 结合微信官方原生框架、uniapp等小程序框架,记录请求、封装、tabbar、UI组件的学习记录和使用技巧等 |
| 《AIGC相关博客》 | 持续更新中~ | AIGC、AI生产力工具的介绍,例如stable diffusion这种的AI绘画工具安装、使用、技巧等总结 |
| 《HTML网站开发相关》 | 《前端基础入门三大核心之html相关博客》 | 前端基础入门三大核心之html板块的内容,入坑前端或者辅助学习的必看知识 |
| 《前端基础入门三大核心之JS相关博客》 | 前端JS是JavaScript语言在网页开发中的应用,负责实现交互效果和动态内容。它与HTML和CSS并称前端三剑客,共同构建用户界面。 通过操作DOM元素、响应事件、发起网络请求等,JS使页面能够响应用户行为,实现数据动态展示和页面流畅跳转,是现代Web开发的核心 | |
| 《前端基础入门三大核心之CSS相关博客》 | 介绍前端开发中遇到的CSS疑问和各种奇妙的CSS语法,同时收集精美的CSS效果代码,用来丰富你的web网页 | |
| 《canvas绘图相关博客》 | Canvas是HTML5中用于绘制图形的元素,通过JavaScript及其提供的绘图API,开发者可以在网页上绘制出各种复杂的图形、动画和图像效果。Canvas提供了高度的灵活性和控制力,使得前端绘图技术更加丰富和多样化 | |
| 《Vue实战相关博客》 | 持续更新中~ | 详细总结了常用UI库elementUI的使用技巧以及Vue的学习之旅 |
| 《python相关博客》 | 持续更新中~ | Python,简洁易学的编程语言,强大到足以应对各种应用场景,是编程新手的理想选择,也是专业人士的得力工具 |
| 《sql数据库相关博客》 | 持续更新中~ | SQL数据库:高效管理数据的利器,学会SQL,轻松驾驭结构化数据,解锁数据分析与挖掘的无限可能 |
| 《算法系列相关博客》 | 持续更新中~ | 算法与数据结构学习总结,通过JS来编写处理复杂有趣的算法问题,提升你的技术思维 |
| 《IT信息技术相关博客》 | 持续更新中~ | 作为信息化人员所需要掌握的底层技术,涉及软件开发、网络建设、系统维护等领域的知识 |
| 《信息化人员基础技能知识相关博客》 | 无论你是开发、产品、实施、经理,只要是从事信息化相关行业的人员,都应该掌握这些信息化的基础知识,可以不精通但是一定要了解,避免日常工作中贻笑大方 | |
| 《信息化技能面试宝典相关博客》 | 涉及信息化相关工作基础知识和面试技巧,提升自我能力与面试通过率,扩展知识面 | |
| 《前端开发习惯与小技巧相关博客》 | 持续更新中~ | 罗列常用的开发工具使用技巧,如 Vscode快捷键操作、Git、CMD、游览器控制台等 |
| 《photoshop相关博客》 | 持续更新中~ | 基础的PS学习记录,含括PPI与DPI、物理像素dp、逻辑像素dip、矢量图和位图以及帧动画等的学习总结 |
| 日常开发&办公&生产【实用工具】分享相关博客》 | 持续更新中~ | 分享介绍各种开发中、工作中、个人生产以及学习上的工具,丰富阅历,给大家提供处理事情的更多角度,学习了解更多的便利工具,如Fiddler抓包、办公快捷键、虚拟机VMware等工具 |
吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤
非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!