ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

前端开发中的常用工具函数(十):JavaScript 数组遍历

前端开发中的常用工具函数(十):JavaScript 数组遍历 目录一、 传统 for 循环一切的基石1. 语法2. 参数介绍3. 适用业务场景4. 注意事项二、 forEach()朴实无华的执行者1. 语法2. 参数介绍3. 适用业务场景4. 注意事项三、 for...of现代异步遍历之王 (ES6)1. 语法2. 参数介绍3. 适用业务场景4. 注意事项四、 map()数据映射器1. 语法2. 参数介绍3. 适用业务场景4. 注意事项五、 filter()数据过滤器1. 语法2. 参数介绍3. 适用业务场景4. 注意事项六、 reduce()全能聚合器1. 语法2. 参数介绍3. 适用业务场景4. 注意事项七、 some() 和 every()布尔值质检员1. 语法2. 参数介绍3. 适用业务场景4. 注意事项八、 for...in请勿用于数组遍历1. 语法2. 参数介绍3. 适用业务场景4. 注意事项针对数组总结如何选择在前端开发中我们每天都在和数组打交道。从渲染列表数据、处理接口返回的 JSON到复杂的表单校验几乎 80% 的业务逻辑都离不开数组遍历。JavaScript 为我们提供了极其丰富的数组遍历方法。一、 传统for循环一切的基石最原始、最硬核的遍历方式。通过控制索引来访问数组元素。1. 语法for (let i 0; i arr.length; i) { // 执行代码 arr[i] }2. 参数介绍i循环控制变量索引。arr.length数组的长度作为循环的边界条件。3. 适用业务场景复杂的索引计算比如需要跳跃式遍历i 2或者需要倒序遍历数组。性能极致要求在处理超大型数组如十万级以上数据时for循环的执行速度通常是最快的。4. 注意事项易引发差一错误需要手动维护边界条件还是极易写错导致越界。作用域问题早期使用var声明i会导致变量泄露到全局现代开发必须使用let声明。优点极其灵活可以通过break跳出循环通过continue跳过本次循环可以倒序遍历。缺点语法较为繁琐需要手动维护索引容易发生越界错误差一错误。const arr [Vue, React, Angular]; for (let i 0; i arr.length; i) { console.log(i, arr[i]); }二、forEach()朴实无华的执行者数组原生的遍历方法它接收一个回调函数对数组中的每个元素执行一次。1. 语法array.forEach(function(currentValue, index, array), thisArg)2. 参数介绍currentValue数组中当前正在处理的元素。index可选当前元素在数组中的索引。array可选正在遍历的数组本身极少用到。thisArg可选执行回调时用作this的值。3. 适用业务场景纯粹的副作用操作例如遍历 DOM 节点列表批量添加事件监听或者遍历日志数组批量打印。修改对象数组属性例如遍历用户列表给每个用户对象动态添加一个isChecked: false属性。4. 注意事项无法中途跳出不能用break或continue会直接报语法错误。不支持await如果回调函数中有异步请求forEach不会等待异步完成容易导致数据不同步。无法return终止外层函数在forEach里return只是结束当前回调相当于for循环里的continue。const arr [Vue, React, Angular]; arr.forEach((item, index, array) { console.log(index, item); // array 就是原数组 arr });三、for...of现代异步遍历之王 (ES6)for...of是 ES6 引入的语法用于遍历可迭代对象包括数组、Map、Set、字符串等但不包括普通对象。1. 语法for (let variable of iterable) { // 执行代码 }2. 参数介绍variable每次迭代时接收来自iterable的值。iterable被遍历的可迭代对象。3. 适用业务场景包含异步操作的遍历例如需要按顺序请求多个接口并处理结果for...of完美支持await。需要提前终止的循环遇到特定条件需要break跳出或continue跳过。遍历 Map 或 Set 结构这是最直观、最推荐的方式。4. 注意事项不遍历普通对象普通对象默认不是可迭代的如果需要遍历对象的键值对请使用Object.entries(obj)配合for...of。拿索引稍显繁琐如果业务逻辑重度依赖索引需要配合arr.entries()解构使用for (const [index, item] of arr.entries())。优点语法最简洁直观直接拿到值。支持break、continue和return。完美支持await如果在async函数中for...of会等待上一个异步操作完成再进行下一次循环。const arr [Vue, React, Angular]; for (const item of arr) { console.log(item); // 直接拿到值不需要写 arr[index] if (item React) break; // ✅ 可以正常跳出 } // 如果需要索引可以结合 entries() for (const [index, item] of arr.entries()) { console.log(index, item); }四、map()数据映射器map的意思是“映射”。它遍历数组对每一项执行回调函数并返回一个全新的数组。新数组的长度和原数组一致。1. 语法let newArray array.map(function(currentValue, index, array), thisArg)2. 参数介绍currentValue当前处理的元素。index可选当前元素的索引。array可选原数组。thisArg可选执行回调时用作this的值。返回值一个由回调函数返回值组成的新数组。3. 适用业务场景数据结构转换将后端返回的数据结构转换为前端组件如 Vue/React 组件需要的数据结构。提取属性从对象数组中提取某个属性组成新数组如users.map(user user.name)。4. 注意事项必须有返回值如果回调函数里忘记return新数组里会全是undefined。不可变性map不会改变原数组但它返回的是浅拷贝。如果数组元素是对象在新数组中修改对象属性会影响原数组中的对象。优点函数式编程风格不修改原数组不可变性链式调用极其方便。缺点如果不使用返回值用map就是浪费内存。const userList [ { id: 1, name: 张三 }, { id: 2, name: 李四 } ]; // 提取所有用户的姓名组成新数组 const names userList.map(user user.name); console.log(names); // [张三, 李四]⚠️注意很多人错把map当成forEach用如果不需要返回新数组请用forEach或for...of五、filter()数据过滤器顾名思义filter用于过滤数组。它遍历数组的每一项回调函数返回true的项会被保留最终返回一个新数组。1. 语法let newArray array.filter(function(currentValue, index, array), thisArg)2. 参数介绍currentValue当前处理的元素。index可选当前元素的索引。array可选原数组。返回值一个通过测试的新数组。如果没有通过测试的元素返回空数组[]。3. 适用业务场景搜索过滤前端实现模糊搜索过滤出包含关键字的数据。条件筛选如从订单列表中筛选出“已付款”状态的订单。4. 注意事项返回布尔值回调函数内部必须返回一个条件表达式真值或假值如果无脑return true则相当于深拷贝。空数组处理filter不会对空数组进行检测。优点语义清晰专门用于筛选数据保持原数组不变。const numbers [1, 2, 3, 4, 5, 6]; // 找出所有偶数 const evens numbers.filter(num num % 2 0); console.log(evens); // [2, 4, 6]六、reduce()全能聚合器reduce是数组方法中最强大、但也最常被初学者忽视的方法。它接收一个回调函数和一个初始值遍历数组将每一项“累积”处理最终返回一个单一的值。1. 语法let result array.reduce(function(accumulator, currentValue, index, array), initialValue)2. 参数介绍accumulator(累计器)上一次回调函数的返回值或者是初始值initialValue。currentValue当前处理的元素。index可选当前索引。如果提供了initialValue从 0 开始否则从 1 开始。array可选原数组。initialValue强烈建议提供作为第一次调用回调函数时的第一个参数的值。3. 适用业务场景统计计算计算购物车商品总价、统计数组中各个元素出现的次数。数组转对象将[{id: 1, name: 张三}]转换为{1: {name: 张三}}方便快速查找。多维数组扁平化配合空数组作为初始值将多维数组拍平。4. 注意事项一定要传初始值如果不传initialValuereduce会默认使用数组的第一个元素作为初始值并跳过第一次循环。如果数组为空且未提供初始值会直接报错Reduce of empty array with no initial value。必须返回累计器回调函数最后必须return accumulator或更新后的累计值否则下一次循环拿到的就是undefined。优点功能极强可以做求和、扁平化数组、分组、甚至实现map和filter的组合功能。const cart [ { name: 键盘, price: 200 }, { name: 鼠标, price: 100 }, { name: 显示器, price: 1000 } ]; // 计算购物车总价 const totalPrice cart.reduce((acc, item) acc item.price, 0); console.log(totalPrice); // 1300 // 进阶将数组按属性分组 const grouped cart.reduce((acc, item) { const type item.price 500 ? 贵 : 便宜; if (!acc[type]) acc[type] []; acc[type].push(item); return acc; }, {});七、some()和every()布尔值质检员这两个方法用于判断数组中的元素是否满足某种条件返回值为布尔值true/false。它们非常高效因为一旦能确定结果就会提前终止遍历。some()只要有一项满足条件就返回true相当于逻辑或||。every()必须所有项都满足条件才返回true相当于逻辑与。1. 语法let boolean array.some(function(currentValue, index, array), thisArg) let boolean array.every(function(currentValue, index, array), thisArg)2. 参数介绍currentValue当前元素。index可选当前索引。array可选原数组。返回值true或false。3. 适用业务场景some(相当于逻辑或||)判断数组中是否“存在”满足条件的元素。例如判断用户列表里是否有管理员权限只要有一个就返回 true。every(相当于逻辑与)判断数组是否“全员满足”条件。例如提交表单前校验所有必填项是否都填写完毕。适用场景表单全量校验、权限校验判断用户是否拥有某个权限标识。4. 注意事项短路特性some找到第一个符合条件的就会停止遍历并返回trueevery找到第一个不符合条件的就会停止遍历并返回false。利用这个特性可以提升性能。空数组的表现对于空数组some返回false而every返回true数学上的逻辑因为不存在不满足条件的项。const scores [80, 90, 60, 75]; // 检查是否有不及格的只要有一个 60 就返回 true const hasFail scores.some(score score 60); console.log(hasFail); // true // 检查是否全员都及格必须所有 60 才返回 true const isAllPass scores.every(score score 60); console.log(isAllPass); // false八、for...in请勿用于数组遍历虽然for...in也能遍历数组但它是为遍历对象的枚举属性而设计的。如果用在数组上会带来隐患1. 语法for (let key in object) { // 执行代码 }2. 参数介绍key对象属性的键名在数组中是字符串形式的索引 “0”, “1”。object被遍历的对象。3. 适用业务场景仅用于遍历普通对象的键值对。4. 注意事项针对数组会遍历自定义属性如果你给数组对象加了自定义属性arr.name testfor...in会把它也遍历出来。顺序不可靠在某些旧引擎下遍历顺序可能不按照数组的物理顺序。性能差因为它需要去查找原型链上的可枚举属性。结论永远不要用for...in遍历数组缺点遍历的是键名字符串形式的索引不是值。会遍历出数组对象自定义的可枚举属性。遍历顺序不保证某些旧引擎下。const arr [a, b, c]; arr.customProp hello; // 给数组加了自定义属性 for (const key in arr) { console.log(key); // 0, 1, 2, customProp ❌ 遍历出了索引和自定义属性 }总结如何选择面对这么多方法记住下面这张决策树保证你不会选错我想把数组变成另一种形态的新数组 ➡️map()我想筛选出符合条件的新数组 ➡️filter()我想把数组合并成一个值求和、转对象 ➡️reduce()我只想知道数组里有没有某项满足条件 ➡️some()/every()我想对每一项执行副作用打印、修改外部变量且没有异步 ➡️forEach()循环里有async/await或者需要break跳出 ➡️for...of需要极其复杂的索引控制或追求极致性能 ➡️ 传统for循环
返回列表