选择数组 API 时先回答两个问题:要返回新数组还是修改原数组?是否需要提前退出循环?map、filter、slice 返回新数组;splice、sort、reverse 会修改原数组;需要 break、continue 或 await 时优先使用 for...of。
本文合并旧版数组方法、slice/splice 和循环方式文章。
目录
- 修改原数组还是返回新数组
- 添加与删除
- slice 与 splice
- map、filter 与 reduce
- find、some 与 every
- sort 与 toSorted
- for、forEach、for...of、for...in
- 选择速查
- 参考资料
修改原数组还是返回新数组
| 返回新数组/值 | 修改原数组 |
|---|---|
map、filter、slice、concat |
push、pop、shift、unshift |
toSorted、toReversed、toSpliced |
sort、reverse、splice |
find、some、every、reduce |
fill、copyWithin |
React state 和 reducer 中通常优先使用不修改原值的方法,避免引用未变化或旧状态被意外改写。
添加与删除
const tools = ["Raycast", "IINA"]
tools.push("AppCleaner") // 修改 tools
const last = tools.pop() // 修改 tools 并返回删除项
需要保持原数组不变:
const withBrew = [...tools, "Homebrew"]
const withoutIINA = tools.filter((tool) => tool !== "IINA")
slice 与 splice
slice(start, end) 返回 [start, end) 范围的新数组,不修改原数组:
const values = [0, 1, 2, 3, 4]
values.slice(1, 4) // [1, 2, 3]
values // 仍为 [0, 1, 2, 3, 4]
splice(start, deleteCount, ...items) 直接修改原数组,并返回被删除项:
const values = ["a", "b", "c"]
const removed = values.splice(1, 1, "B", "B2")
removed // ["b"]
values // ["a", "B", "B2", "c"]
现代运行环境还可以用 toSpliced 返回新数组:
const next = values.toSpliced(1, 1, "new")
map、filter 与 reduce
转换每一项使用 map:
const names = tools.map((tool) => tool.name)
筛选使用 filter:
const freeTools = tools.filter((tool) => tool.price === 0)
聚合为单个结果使用 reduce:
const total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0)
如果 reduce 回调需要一大段解释,普通循环往往更清楚。不要为了“函数式”把可读逻辑压成一行。
find、some 与 every
const iina = tools.find((tool) => tool.slug === "iina")
const hasFreeTool = tools.some((tool) => tool.price === 0)
const allAvailable = tools.every((tool) => tool.available)
find返回第一个匹配元素或undefined。some找到一个 true 就停止。every遇到一个 false 就停止。
只需要判断存在性时不要先 filter 再检查长度。
sort 与 toSorted
sort 会修改原数组,并且默认按字符串比较:
[10, 2, 30].sort() // [10, 2, 30],不是数值升序
数值排序:
const ascending = numbers.toSorted((a, b) => a - b)
如果环境不支持 toSorted:
const ascending = [...numbers].sort((a, b) => a - b)
比较函数要保持稳定规则,不要返回随机值。
for、forEach、for...of、for...in
for
需要索引、双指针或精确步长:
for (let index = 0; index < items.length; index += 1) {
if (items[index].hidden) continue
render(items[index])
}
forEach
适合对数组每项执行同步副作用,但不能 break,也不会等待 async 回调:
items.forEach((item) => console.log(item.id))
不要这样等待:
items.forEach(async (item) => {
await save(item)
})
for...of
适合可迭代对象、提前退出和顺序 await:
for (const item of items) {
if (!item.valid) break
await save(item)
}
for...in
for...in 遍历对象的可枚举字符串属性名,不推荐用于数组:
for (const key in settings) {
console.log(key, settings[key])
}
对象自有属性通常可用 Object.keys、Object.values 或 Object.entries 明确处理。
选择速查
| 需求 | 推荐 |
|---|---|
| 每项转换 | map |
| 保留符合条件的项 | filter |
| 查找第一项 | find |
| 判断是否存在 | some |
| 汇总 | reduce 或清晰循环 |
| 截取但不修改 | slice |
| 原地插入删除 | splice |
| 需要 break/continue/顺序 await | for...of |
| 需要索引控制 | for |
对象解构、浅拷贝和深拷贝见 JavaScript 对象复制指南,余数与循环索引见 JavaScript % 运算指南。
参考资料
继续阅读
JavaScript Event Loop:任务、微任务与渲染顺序
通过调用栈、任务队列、微任务检查点和浏览器渲染流程,解释 Promise、queueMicrotask、setTimeout 与 async/await 的真实执行顺序和常见性能陷阱。
7 分钟JavaScript % 余数运算:负数、取模与实用场景
解释 JavaScript % 返回余数而非数学取模,展示负数归一化、奇偶判断、循环索引、角度归一化和 BigInt 的正确写法。
11 分钟JavaScript 解构与对象复制:浅拷贝、深拷贝
讲清对象和数组解构、展开语法的浅拷贝边界,并比较 structuredClone、JSON 序列化与自定义深拷贝的适用场景。
订阅 FreeMac
每周精选:免费 Mac 软件评测、可信来源更新、替代方案和少折腾指南。