news 2026/9/30 15:29:18

别再只会for循环:JavaScript数组遍历七法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
别再只会for循环:JavaScript数组遍历七法

做前端这几年,我面试过不少人,也看过不少新人写的代码。有个现象特别有意思:很多写了两三年 JavaScript 的人,遇到数组遍历要么只会用 for 循环死磕,要么无脑用 map 或者 forEach,压根没搞清楚每个方法返回什么、什么时候该用哪个。今天就把 JavaScript 数组遍历里最常用的七个方法讲透:forEach、map、filter、reduce、every、some、find。

这些方法看起来都只是“循环遍历数组”,但背后的设计思路完全不同:有的用来执行副作用,有的用来做数据映射,有的用来做筛选,有的用来做聚合,还有的能提前终止遍历。这篇文章会用实际场景拆解它们的差异、底层机制和藏在细节里的坑,无论你是刚入门的新手,还是写了几年业务代码但没系统梳理过的老手,都能有所收获。

1. 遍历之前:先搞清楚你要什么

数组遍历的本质,就是对数组里的每个元素执行一次回调函数。回调函数统一接收三个参数:当前元素item、当前索引index、数组本身array。这个签名在 forEach、map、filter、reduce 里基本一致,只有 reduce 例外,它额外多一个“累计值”参数。

但“遍历”两个字掩盖了方法之间的巨大差异。同样是走一遍数组,编译器的意图可以是:

  • 只是想对每个元素做点事(比如打印、赋值);
  • 想根据每个元素生成一个新元素,最终得到等长的新数组;
  • 想把不满足条件的元素剔除,保留子集;
  • 想找到第一个符合条件的元素;
  • 想确认是否存在至少一个/所有元素满足条件;
  • 想把整个数组合并成一个值。

如果你没想清楚自己要什么就去选方法,代码大概率会写得别扭。比如有人用 map 做遍历打印,有人用 forEach 收集结果还要额外定义外部变量,还有人为了找一个元素把整个数组 filter 一遍再取[0]。这些都是没有按照“返回值”来选方法导致的。

先用一张速览表把这些方法的关键差异列出来,后面再逐个拆。

方法返回值是否生成新数组能否提前停止典型用途
forEachundefined否不能执行副作用(打印、写入)
map新数组,长度与原数组相同是不能数据映射、转换
filter新数组,长度为满足条件的元素个数是不能(会遍历全部)筛选子集
find第一个满足条件的元素值,没有则 undefined否找到后可以停止查找单个元素
some布尔值否遇到 true 停止是否存在满足条件
every布尔值否遇到 false 停止是否全部满足
reduce累计后的任意类型值否不能(除非手动 throw)求和、分组、扁平化

这张表你得牢记一点:方法名带“返回什么”,就决定了它适合什么场景。比如 map 核心是“映射”,不是“遍历”;filter 核心是“筛选”,筛选必然产生新数组;some/every 是“判断”,结果必须是布尔值。想通这一点,你选方法时就不会犹豫。

2. 逐个拆解:七个方法的核心用法与原理

2.1 forEach:只做事,不返回

forEach 的语法长这样:

arr.forEach(callback(currentValue, index, array) { // 你的操作 }, thisArg);

它非常简单,拿到数组每个元素依次执行回调,没有返回值,返回值是 undefined。这意味着你用const res = arr.forEach(...)拿到的永远是 undefined。

forEach 适合什么场景?“副作用”场景,也就是你想对数组里的每个元素执行一些操作,但操作的结果不需要再组合成一个新数组。比如:

const users = [{ name: '张三' }, { name: '李四' }]; users.forEach((user, index) => { console.log(`第 ${index + 1} 个用户是:${user.name}`); }); // 输出: // 第 1 个用户是:张三 // 第 2 个用户是:李四

还有常见的场景是把表单数据逐条提交、记录埋点、给每个 DOM 节点绑定事件。这些操作的特点是“做完就完了”,不需要把结果收集回来。

很多人刚学写代码时喜欢这样:

const result = []; arr.forEach(item => { if (item.age > 18) { result.push(item); } });

这种写法能用,但属于“用 forEach 的副作用去模拟 filter + map 的组合”。本质问题是:你用外部数组result作为容器,违背了函数式编程中“不变性”的原则。更好的做法是直接arr.filter(item => item.age > 18),后面会讲。

一个容易忽略的点:forEach 不会修改原数组,但如果数组里的元素是对象,回调中修改对象的属性是会生效的。因为对象是引用传递,你改的是对象本身。比如:

const arr = [{ count: 1 }]; arr.forEach(item => { item.count += 1; }); console.log(arr[0].count); // 2

这不算 bug,但要意识到 forEach 的“不改变原数组”只是说你不能通过回调的返回值去组合一个新数组,或者对原始数组进行结构层面的增删。

2.2 map:映射,不是遍历

map 是“映射”的缩写。它的定义是:对数组每一项执行回调,把回调的返回值收集起来,构成一个等长的新数组。

const newArr = arr.map(callback(currentValue, index, array) { // 必须 return,否则新数组对应位置是 undefined return transformedValue; });

举个最常见的例子:取对象数组中某个字段。

const users = [ { name: '张三', age: 20 }, { name: '李四', age: 25 }, ]; const names = users.map(user => user.name); console.log(names); // ['张三', '李四']

再比如把数字都翻倍:

const numbers = [1, 2, 3]; const doubled = numbers.map(n => n * 2); console.log(doubled); // [2, 4, 6]

map 最大的坑是:回调忘了写 return。新手经常写出这样的代码:

const arr = [1, 2, 3]; const doubled = arr.map(item => { item * 2; // 没有 return }); console.log(doubled); // [undefined, undefined, undefined]

因为花括号{}把函数体包起来了,必须显式return。所以要么写单行表达式arr.map(item => item * 2),要么在函数体里加上return。

另一个容易被忽略的点是:map 出来的新数组长度与原数组相同,但元素的值由回调决定。如果你不需要产生新的数组内容,只是想遍历,用 forEach 更合适——用 map 做循环会产生一个没人要的临时数组,浪费内存且语义混乱。

2.3 filter:筛选出符合条件的新数组

filter 的作用是“过滤”。它会对原数组每一项执行回调,回调返回值为true(或真值)时,把这一项保留下来,最终返回一个新数组。

const newArr = arr.filter(callback(currentValue, index, array) { return booleanValue; // 如果为 true,保留当前元素 });

经典场景:筛选出年龄大于 18 的用户。

const users = [ { name: '张三', age: 17 }, { name: '李四', age: 21 }, { name: '王五', age: 18 }, ]; const adults = users.filter(user => user.age >= 18); console.log(adults); // [{ name: '李四', age: 21 }, { name: '王五', age: 18 }]

filter 不会修改原数组,总是返回一个新数组。如果没有任何元素满足条件,返回的是空数组[],不是 undefined。

这里有个常见的误解:有人以为 filter 会“删除”原数组中的元素。实际上原数组保持不变。如果你要做删除操作,比如从某个数组中移除 id 为 3 的元素,正确姿势是:

const newArr = arr.filter(item => item.id !== 3);

然后把newArr作为新状态使用,而不是去 splice 原数组。这在 React、Redux 这类强调不可变数据的环境中几乎是标配操作。

filter 的性能也值得说一句:它会完整遍历数组,所以如果你只想找一个满足条件的元素,用 filter 会做很多无用功,这时应该用find。

2.4 find:只找第一个,找到就停

find 返回第一个满足回调条件的元素值,如果没有,返回undefined。

const foundItem = arr.find(callback(currentValue, index, array) { return booleanValue; });

它和 filter 最大的区别是:filter 返回包含所有满足条件的数组,find 只返回第一个命中对象。比如:

const users = [ { id: 1, name: '张三' }, { id: 2, name: '李四' }, { id: 3, name: '王五' }, ]; const target = users.find(user => user.id === 2); console.log(target); // { id: 2, name: '李四' }

如果找不到,返回 undefined。对应到实际业务里非常常见:根据 ID 找一条订单、根据路由路径匹配一个配置项、在字典数组里查某个状态对应的文本。

find 还有一个隐藏优势:它会在找到第一个满足条件的元素后停止遍历,不再继续检查剩余元素。对应到长列表场景,如果满足条件的元素靠前,效率会比 filter 高不少。虽然数组遍历本身开销不大,但养成正确的习惯还是很有必要的。

有一个小知识点:与 find 配套的findIndex同样很好用,它返回的是索引而不是元素本身。当你只需要索引时,不要arr.find(...)再indexOf,直接用findIndex。

2.5 some 和 every:满足一次还是满足所有

把 some 和 every 放一起,因为它们是“逻辑判断”的一对。它们的返回值都是布尔值。

// some:只要有一个元素满足条件,就返回 true,否则 false const hasAdult = arr.some(item => item.age >= 18); // every:所有元素都满足条件,才返回 true,否则 false const allAdults = arr.every(item => item.age >= 18);

这两个方法在语义上分别等同于逻辑或(||)和逻辑与(&&)在数组上的展开。比如:

const numbers = [1, 2, 3, 4, 5]; // 有没有偶数? console.log(numbers.some(n => n % 2 === 0)); // true // 是否全部都是正数? console.log(numbers.every(n => n > 0)); // true // 是否全部都是偶数? console.log(numbers.every(n => n % 2 === 0)); // false

这里有一个值得利用的特性:some 只要回调返回 true 就会立刻停止遍历,every 遇到 false 会立刻停止遍历。这是它们和 forEach/map/filter 不一样的地方,你可以利用这个特性实现提前退出。

举个例子,判断一个数组中是否包含某个值,传统做法用indexOf或者 includes,但如果是“是否包含某个满足复杂条件的对象”,some 是最好的选择:

const users = [ { name: '张三', role: 'admin' }, { name: '李四', role: 'user' }, ]; const hasAdmin = users.some(user => user.role === 'admin'); console.log(hasAdmin); // true

同理,如果需要校验“所有输入都不为空”,可以:

const values = ['abc', 'def', '']; const allValid = values.every(value => value.trim() !== ''); console.log(allValid); // false

every 和 some 同样不会改变原数组。它们适合放在if条件里做校验,而不是用来收集数据。

2.6 reduce:所有方法的底层能力,都在这了

reduce 是这七个方法里最强大、也最容易让人懵圈的一个。它的回调签名和前面几个不同,有四个参数:

arr.reduce(callback(accumulator, currentValue, currentIndex, array) { // accumulator 是上一次回调的返回值 // 返回的结果会作为下一次调用的 accumulator return newAccumulator; }, initialValue);

第一次执行时,accumulator的取值分两种情况:

  • 如果你提供了initialValue,第一次的accumulator就是initialValue,第一次的currentValue是数组第一个元素。
  • 如果你没提供初始值,第一次的accumulator是数组第一个元素,currentValue是数组第二个元素。也就是说,reduce 会从第二个元素开始遍历。

最经典的求和:

const numbers = [1, 2, 3, 4]; const sum = numbers.reduce((acc, cur) => acc + cur, 0); console.log(sum); // 10

这行代码等价于:

let sum = 0; for (const cur of numbers) { sum += cur; }

注意,如果不传初始值,写成:

const sum = [1, 2, 3, 4].reduce((acc, cur) => acc + cur);

也能得到 10,因为第一次 acc 是 1,cur 是 2,累加后 acc 变成 3,然后 cur 是 3,以此类推。但有一个致命问题:数组为空时,不传初始值会直接抛错。所以除非你确定数组至少有一个元素,否则最好始终传入初始值。

reduce 不只能做加法,它能做任何“把一个数组归并成单个值”的操作。后面我会专门讲几个进阶案例,比如分组、统计词频、把数组转对象等。

3. 进阶用法与组合实战

3.1 链式调用:一出手就是清晰的数据管道

这几个方法最爽的用法是串联起来,形成一个“数据管道”。每一个方法返回的值能被下一个方法接收,中间不会产生冗余的中间变量。

假设你的业务是:从一堆订单中筛出已付款订单,把它们金额打 9 折,再算出总金额。

const orders = [ { id: 1, paid: true, amount: 100 }, { id: 2, paid: false, amount: 50 }, { id: 3, paid: true, amount: 200 }, ]; const total = orders .filter(order => order.paid) // 1. 筛选已付款 .map(order => order.amount * 0.9) // 2. 计算折扣价 .reduce((sum, price) => sum + price, 0); // 3. 求和 console.log(total); // 270

这不是炫技。你想想如果用循环怎么写?得写三层:先建数组存过滤结果,再建数组存打折金额,最后累计。链式调用每一步只做一件事,变量名都不需要额外定义,可读性反而更好。

不过要注意:链别太长。一旦超过四五个方法,别人阅读时就需要在脑子里同时维护每一步的数据状态,反而降低了可读性。如果中间处理逻辑很复杂,建议拆成带语义的变量,或者用命名函数:

const paidOrders = orders.filter(order => order.paid); const discountedPrices = paidOrders.map(order => order.amount * 0.9); const total = discountedPrices.reduce((sum, price) => sum + price, 0);

拆开写更容易打断点调试。我自己一般是先写成链式,等改出 bug 再拆开打 log,最终提交前看阅读难度决定是否保留。

3.2 用 reduce 做分组和统计

说实话,reduce 最实用的进阶场景不是求和,而是“把数组变成对象”。比如把一堆用户按照角色分组,你会怎么做?

有人会手写 for 循环,维护一个空对象,然后判断 key 是否存在。用 reduce 一行搞定:

const users = [ { name: '张三', role: 'admin' }, { name: '李四', role: 'user' }, { name: '王五', role: 'admin' }, ]; const groupByRole = users.reduce((result, user) => { // 如果当前角色还没有分组,先初始化一个空数组 if (!result[user.role]) { result[user.role] = []; } result[user.role].push(user); return result; }, {}); console.log(groupByRole); // { // admin: [{ name: '张三', role: 'admin' }, { name: '王五', role: 'admin' }], // user: [{ name: '李四', role: 'user' }] // }

关键点在于:reduce 的回调必须返回result。很多初学者在这里忘记 return,导致第二次回调时result变成 undefined,然后if (!result[user.role])直接报错。如果你也踩过这个坑,记住一句话:reduce 回调的返回值会成为下一次回调的第一个参数,不 return 就断链。

同样,统计数组中每个单词出现的次数:

const words = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']; const frequency = words.reduce((counter, word) => { counter[word] = (counter[word] || 0) + 1; return counter; }, {}); console.log(frequency); // { apple: 3, banana: 2, orange: 1 }

顺便一提,像这种“数组转对象”“按 key 分组”的逻辑,在很多业务代码里反复出现。ECMAScript 新规范里也提供了Object.groupBy之类的 API,但浏览器兼容性还不够全面,完全可以用 reduce 自己实现,不依赖任何库。

3.3 在 forEach 里如何“中断”:三种替代思路

很久以前我也困惑过:为什么forEach里的break会直接报语法错误?这个得从设计意图说起。forEach 的设计目标是“对每个元素无差别地执行回调”,它是一个声明式 API,不承诺你可以在中途退出。换句话说,它的底层实现是一个只会走到底的循环,不像for...of那样受break控制。

那如果业务上确实需要在遍历到某个元素后提前结束呢?有三种常见做法。

第一种:用some()替代 forEach 实现“找到即停止”。

如果你是在“找有没有满足条件的元素”,那 some 本身就是为这个场景设计的。回调返回 true 时,some 立刻返回 true,后面不再遍历。

const arr = [1, 2, 3, 4, 5]; const found = arr.some(item => { console.log('当前值:', item); if (item === 3) { return true; // 返回 true 后,some 立刻停止 } return false; }); // 只会打印到 3

第二种:用find()替代,找到后直接拿结果。

如果目标是“找到元素本身”,find 天然支持提前停止,前面已经讲过。

第三种:改用for...of循环,用 break 退出。

如果你必须在遍历过程中根据条件跳出,且逻辑并不是简单的“查找”,那就老老实实用最原始的循环,不要硬套数组方法:

const arr = [1, 2, 3, 4, 5]; for (const item of arr) { if (item === 3) break; console.log(item); }

有人可能想用“抛异常”来中断 forEach,这确实能让遍历停下来,但异常本身的成本不低,而且会让代码陡增复杂度。除非你写的是一个 do-while 风格的复杂遍历,否则我强烈不建议为“中断”小心翼翼去 try-catch。

3.4 异步遍历的坑:forEach 不等人

这是实战里翻车率最高的一个问题。如果你在forEach里写await,你会发现根本不等你:

async function handleList(items) { items.forEach(async (item) => { await saveToServer(item); // 并不会按顺序等待 }); console.log('全部完成'); // 这行会先执行 }

原因很简单:forEach不会理会回调返回的 Promise。它只是把每个回调丢出去执行了,然后在继续遍历下一个元素。await 只作用在回调函数内部,没有作用到 forEach 层面。所以如果你需要串行地一个个等异步操作完成,有两个方案。

方案一:用for...of,这是最简单的串行写法。

async function handleList(items) { for (const item of items) { await saveToServer(item); // 每循环一次,等下一次完成 } console.log('全部完成'); }

方案二:如果异步操作之间互不依赖,希望并发执行,用Promise.all加上 map:

async function handleList(items) { await Promise.all(items.map(item => saveToServer(item))); console.log('全部完成'); }

这里用map而不是forEach,是因为 map 会收集每个回调返回的 Promise。如果你用 forEach,回调的返回值被丢弃,Promise.all就无法拿到这些 Promise。这个差别是“返回值”设计带来的关键影响,也是我反复强调“方法返回值决定了它的用途”的原因。

4. 常见问题与排查实录

4.1 记不住回调参数顺序怎么办

这是新手最常问的问题。所有遍历方法的回调都是(item, index, array)的顺序,也就是:

  • 第一个参数是元素本身;
  • 第二个参数是下标;
  • 第三个参数是正在遍历的原数组。

只有 reduce 例外,它的回调是(accumulator, item, index, array),也就是累计值要放在第一个参数。

你可以通过一个小技巧记忆:先判断“这个东西是不是每次遍历都有的东西”,如果是元素,就排在前面。reduce 多了累计值,累计值又是最“特殊”的,所以它排在第一位。

4.2 map 和 forEach 到底选哪个

这是面试必问题。一句话版答案:需要生成一个与输入数组一一对应的新数组时用 map,不需要返回值只是要执行操作时用 forEach。

细节版:map 的返回值是一个新数组,而且长度和原数组完全相同。如果你用 map 却不要它的返回值,等于浪费了一部分内存还降低了代码可读性。比如:

// 不推荐:为了遍历而用 map,产生的数组丢了 [1, 2, 3].map(item => { console.log(item); }); // 推荐:forEach 就是为这种场景设计的 [1, 2, 3].forEach(item => { console.log(item); });

反之,如果你要得到一个新数组,也不要用 forEach 加外部数组。比如:

// 不推荐:手动 push 掩盖了“映射”语义 const result = []; [1, 2, 3].forEach(item => { result.push(item * 2); }); // 推荐:map 本身就是做映射 const result = [1, 2, 3].map(item => item * 2);

4.3 filter 和 find 使用场景混淆

一句话版:要所有满足条件的元素,用 filter;只要第一条满足条件的记录,用 find。

我见过不少代码这样写:

const user = users.filter(user => user.id === userId)[0];

如果确定 id 唯一,这里更合适的应该是users.find(user => user.id === userId)。filter 会遍历完整个数组,把所有相同 id 的都筛出来(虽然只有一条),再通过下标取第一个。find 会在找到第一条后立刻返回,语义也更直接。

当然,如果你的目的是“得到所有匹配项”,那就该用 filter,这没悬念。这里还有一个小的知识点:filter 即使没有任何匹配项,返回的也是空数组,所以可以用filter(...).length > 0来判断是否存在。但既然有some和find,直接用它们做判断和取元素会更好,也不用担心返回 undefined 的问题。

4.4 遍历过程中修改原数组引发的诡异问题

很多人不知道 JS 数组遍历方法不推荐在回调中直接对数组进行 push、pop、splice 等操作。比如:

const arr = [1, 2, 3, 4, 5]; arr.forEach(item => { if (item === 2) { arr.push(999); } console.log(item); }); // 打印 1 2 3 4 5

这里的 999 并不会被遍历到,因为 forEach 在遍历前会把数组长度缓存下来。也就是说,你在过程中新增的元素不会出现在本次遍历中。但反过来,如果你用splice删除了元素,又会导致后续索引错乱,甚至跳过元素:

const arr = [1, 2, 3, 4, 5]; arr.forEach((item, index) => { if (item === 3) { arr.splice(index, 1); // 删除当前元素 } }); console.log(arr); // [1, 2, 4, 5]

看起来变化不大,但如果下一个元素的索引是 3,原来索引为 5 的元素会移动到索引 4,而 forEach 不会重新读取数组,它继续访问索引 4,就会拿到移动过来的元素。这种索引混乱特别不容易察觉,尤其是遍历过程中连续删除多个元素时。

我的建议是:不要在遍历时修改数组本身。如果你要删除一批元素,用 filter 生成一个新数组再整体替换;如果你要在遍历过程中对数组做增删,改成 while 循环并手动维护索引,逻辑会清楚很多。

4.5 稀疏数组:map 会保留空洞,forEach 会跳过

JavaScript 的数组允许有“洞”,比如:

const arr = [1, , 3]; // 中间有一项是空位

注意,这里的空位是undefined但又不完全是undefined值,它是数组上的“缺口”。

不同方法的处理方式不同:

  • forEach、filter、some、every会跳过空洞;
  • map会保留空洞,返回的数组中对应位置仍然是空洞;
  • find会把空洞当成undefined进行处理。

我们验证一下:

const arr = [1, , 3]; arr.forEach((item) => { console.log(item); // 输出 1 和 3,第二个元素被跳过 }); const mapped = arr.map(item => item * 2); console.log(mapped); // [2, 空洞, 6],空洞还在 const filtered = arr.filter(item => true); console.log(filtered); // [1, 3],空洞被过滤掉了

如果数据源是后端返回的 JSON 数组,通常不会有空洞,但如果你自己创建数组时用了new Array(3),里面就是三个空洞。所以处理这种数组时,如果需要保证不出现未知的 undefined,可以用Array.from或者展开运算符把空洞转成 undefined:

const arr = Array.from(new Array(3)); // [undefined, undefined, undefined]

4.6 reduce 忘了传初始值,空数组直接报错

刚才提过一句,这里展开说:

const arr = []; const sum = arr.reduce((acc, cur) => acc + cur); // TypeError: Reduce of empty array with no initial value

因为没传初始值时,reduce 会把第一项当成初始值。数组为空,它连“第一项”都没有,只能抛错。所以凡是可能为空数组的统计,必须带上初始值:

const sum = arr.reduce((acc, cur) => acc + cur, 0);

还有一个细节:即使数组非空,如果回调里返回了undefined,下一次的acc就变成 undefined,后续加减乘除很容易变成 NaN。所以 reduce 的回调一定要设置明确的返回值。

另外,如果你用 reduce 统计属性数量和,初始值类型决定了返回类型。比如统计数组中有多少个有效值,如果初始值是 0,结果一定是数字;如果你想返回一个对象,初始值就必须是{}。

5. 选型速查与我的实操总结

5.1 一张表助你直接抄作业

需求使用的方法示例
给每个元素执行操作(打印、埋点)forEacharr.forEach(item => { sendLog(item) })
数组元素逐个转换maparr.map(item => item.price * 2)
筛掉不满足条件的元素filterarr.filter(item => item.isValid)
只需要第一个满足条件的元素findarr.find(item => item.id === 5)
判断是否存在匹配项somearr.some(item => item.score > 60)
判断所有项都符合条件everyarr.every(item => item.score > 60)
把数组聚合为单个值reducearr.reduce((sum, item) => sum + item.count, 0)

如果你拿不准,就按顺序问自己:我要不要新数组?要长度相等的用 map,要子集用 filter;要不要布尔值?用 some/every;要不要单个元素?用 find;要不要执行副作用?用 forEach;要不要合并成一个值?用 reduce。

5.2 我在项目里的实际体会

最后分享几个我个人的经验,不算标准答案,但实战中真的有用。

第一,不要迷信函数式写法。数组方法确实能写出很优雅的代码,但如果不分场景全用链式,debug 时你会很痛苦。我见过最夸张的一段代码,连续调用六个方法,最后返回结果不对,只能一行行拆开加 console.log。这种时候我就会想:与其写出零注释的“高密度管道”,不如用一两个中间变量把每步结果存下来。

第二,在 React 生态里,尽量用 map 不可变更新,少用 forEach 改外部变量。React 和 Redux 的核心理念是“状态不可变”,你放一个外部数组在 forEach 里 push 再 setState,很容易产生被共享的引用,导致状态偷偷被修改。用map、filter生成新数组再赋值,才是符合框架心智模型的做法。

第三,性能差异在绝大多数业务场景里可以忽略。有些工程师为了一个几千长度的数组,把 filter 改成手动 for 循环,说“更快”。实际上,V8 引擎对这几个原生方法都有非常深的优化,多数场景下你这点性能差距远小于网络请求的消耗。真正影响性能的反而是滥用链式,产生多个临时数组。如果数组极大(十万、百万级别),或者需要按索引随机访问和提前中断,我会直接回到 for 或 while。但业务开发中,for 循环应该只作为“最后手段”出现。

第四,写代码前先想清楚方法的返回值。这个我反复强调,因为它真的是根因。只要你想清楚“forEach 返回 undefined、map 返回新数组、filter 返回子集、find 返回元素、some/every 返回布尔、reduce 返回累计值”,你在选方法时完全不用查文档,也不用试错。所谓“数组遍历方法详解”,说到底就是对你“意图”的确认——你想要的到底是过程、结果,还是状态?

这些方法用熟了以后,你会发现 JavaScript 里很多逻辑都可以用“数据管道”的方式写出来。少一些临时变量,少一些副作用,代码会更好维护。希望这篇拆解能让你对数组遍历这件事建立起自己的判断框架,至少下次在代码 review 时,能一眼看出来哪里用了错误的遍历方法。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 15:27:37

极坐标系下暴力采样:原理、算法与雷达点云降采样实践

1. 这个项目到底在解决什么问题 先说结论:用极坐标系做暴力采样,本质上是把“在规则网格上均匀铺点”这件事换了一种坐标系来干,目的往往是为了让样本点在某些维度上分布更合理、更密集,或者更贴合数据的真实形状。 我在实际工作…

作者头像 李华
网站建设 2026/9/30 15:26:48

Claude Code 把自己改成了任务调度器,这次设计比功能更值得看

说实话,我第一次看到 Claude Code v2.1.139 的 changelog,以为只是个普通版本更新——新功能扫了一眼,Agent 视图和 /goal 命令,感觉不就是「任务管理器」和「批量执行」嘛,有什么大惊小怪的。 结果真正用了两天&…

作者头像 李华
网站建设 2026/9/30 15:26:02

Univer开源协同表格引擎:从集成到生产部署的实践指南

如果你最近在调研开源在线表格方案,大概率避不开 Univer 这个名字。它是一套基于 TypeScript 构建的在线协同文档引擎,覆盖电子表格、文档、幻灯片三类场景,既能像 Excel 那样处理复杂公式和多层样式,又能像 Google Sheets 那样支…

作者头像 李华
网站建设 2026/9/30 15:25:31

TensorFlow 2.x 安装、训练与部署实战指南:从数据管道到生产环境

TensorFlow 可能是你在 AI 领域听到最多的名字之一,也是我这些年被问到频率最高的一个词。很多人一上来就问“TensorFlow 和 PyTorch 到底学哪个”,或者“TensorFlow 是不是过气了”,其实这类问题本身就带着一个误解:把深度学习框…

作者头像 李华
网站建设 2026/9/30 15:25:25

Win11运行老软件实测:8款古董级软件兼容性与配置指南

Windows 11出来之后,身边不少朋友问我:以前那些老软件还能不能用?说实话,我自己升级Win11之前也有这个顾虑。右键菜单变了、资源管理器变了、连右键属性界面都成了"新款式",看着哪哪都陌生。但真正用下来才发…

作者头像 李华
网站建设 2026/9/30 15:24:01

Win11+Ubuntu双系统安装实战:从UEFI启动盘到GRUB引导修复全记录

简介:面向已安装Windows 11并希望尝试Ubuntu系统的用户,这份doc文档详细记录了在Win11环境下安装Ubuntu 22.04.1 LTS并实现双系统共存的完整流程。内容从查看系统基础环境开始,依次讲解Ubuntu镜像下载、Rufus制作启动U盘、压缩磁盘空间、BIOS…

作者头像 李华