在日常开发中,我们经常需要合并数组,比如批量导入数据、分页加载列表、处理大量日志等场景。当数组规模较小时,用什么方法都差不多;但当数组达到成千上万条时,选择不当的方法可能会导致栈溢出 或内存飙升。
今天我们就来聊聊 JavaScript 中三种常见的数组合并方式,看看它们各自的优缺点,以及在不同场景下应该如何选择。
三种数组合并方式
1. 扩展运算符 + push:arr.push(...otherArr)
这是最"爽"的写法,一行代码搞定:
javascript
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
arr1.push(...arr2)
console.log(arr1) // [1, 2, 3, 4, 5, 6]
优点:
- 代码简洁,可读性强
- 语法现代,符合 ES6+ 风格
- 适合小数组快速合并
缺点:
- 当数组元素超过约 65536 个时,会触发 JavaScript 引擎的最大参数数量限制,导致栈溢出错误
- 扩展运算符需要先展开数组,会消耗额外内存
- 大数组性能较差
适用场景:
- 小数组(< 1000 个元素)
- 快速脚本开发
- 数量可控的场景
2. concat 方法:arr.concat(otherArr)
这是最"安全"的写法:
javascript
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
const merged = arr1.concat(arr2)
console.log(merged) // [1, 2, 3, 4, 5, 6]
优点:
- 不会导致栈溢出,适合大数组
- 语义清晰,代码可读性好
- 返回新数组,不修改原数组(函数式编程友好)
- 性能稳定,适合生产环境
缺点:
- 会创建新数组,占用额外内存
- 对于超大数组,内存占用会翻倍
适用场景:
- 中大数组(> 1000 个元素)
- 需要保持原数组不变
- 函数式编程风格
- 生产环境安全要求高
3. 循环 push:for (const item of arr) { target.push(item) }
这是最"稳"的写法:
javascript
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
for (const item of arr2) {
arr1.push(item)
}
console.log(arr1) // [1, 2, 3, 4, 5, 6]
优点:
- 最安全,绝对不会栈溢出
- 内存占用最低(原地修改,不创建新数组)
- 性能稳定,适合超大数组
- 兼容性最好
缺点:
- 代码相对冗长
- 需要手动写循环
- 会修改原数组
适用场景:
- 超大数组(> 10000 个元素)
- 内存敏感场景
- 长时间运行的服务端任务
- 需要最高性能的场景
性能对比表
| 方法 | 栈溢出风险 | 内存占用 | 性能 | 代码简洁度 | 适用数组大小 |
|---|---|---|---|---|---|
push(...arr) |
⚠️ 高(>65536) | 中 | 差(大数组) | ⭐⭐⭐⭐⭐ | < 1000 |
concat() |
✅ 无 | 中高 | 好 | ⭐⭐⭐⭐ | > 1000 |
循环 push() |
✅ 无 | 低 | 最好 | ⭐⭐⭐ | > 10000 |
实际案例对比
让我们看看在不同规模下的表现:
小数组(100 个元素)
javascript
// 三种方法都可以,性能差异可忽略
const small = Array.from({ length: 100 }, (_, i) => i)
// 方法1:扩展运算符
arr1.push(...small) // ✅ 推荐
// 方法2:concat
const merged = arr1.concat(small) // ✅ 也可以
// 方法3:循环
for (const item of small) arr1.push(item) // ✅ 也可以,但没必要
中数组(10000 个元素)
javascript
const medium = Array.from({ length: 10000 }, (_, i) => i)
// 方法1:扩展运算符
arr1.push(...medium) // ⚠️ 可能栈溢出!
// 方法2:concat
const merged = arr1.concat(medium) // ✅ 推荐
// 方法3:循环
for (const item of medium) arr1.push(item) // ✅ 也可以
大数组(100000 个元素)
javascript
const large = Array.from({ length: 100000 }, (_, i) => i)
// 方法1:扩展运算符
arr1.push(...large) // ❌ 几乎肯定会栈溢出!
// 方法2:concat
const merged = arr1.concat(large) // ⚠️ 可以,但内存占用高
// 方法3:循环
for (const item of large) arr1.push(item) // ✅ 强烈推荐
进阶优化技巧
1. 分批处理超大数组
当数组特别大时,可以分批处理,避免一次性操作:
javascript
function batchConcat(target, source, batchSize = 5000) {
for (let i = 0; i < source.length; i += batchSize) {
const batch = source.slice(i, i + batchSize)
target.push(...batch) // 小批次可以用扩展运算符
}
return target
}
// 使用
const huge = Array.from({ length: 100000 }, (_, i) => i)
const result = []
batchConcat(result, huge) // 安全处理超大数组
2. 使用 Array.from 和展开运算符组合
对于需要转换的场景:
javascript
// 不推荐:可能栈溢出
const mapped = arr.map(x => x * 2)
result.push(...mapped)
// 推荐:使用 concat
const mapped = arr.map(x => x * 2)
result = result.concat(mapped)
// 或者:直接循环
for (const item of arr) {
result.push(item * 2)
}
3. 流式处理(迭代器)
如果数据来自流或生成器,可以边读边处理:
javascript
async function* fetchData() {
// 模拟分页数据流
for (let i = 0; i < 10; i++) {
yield await fetchPage(i)
}
}
const result = []
for await (const page of fetchData()) {
// 流式处理,内存占用低
result.push(...page) // 每页数据量小,可以用扩展运算符
}
常见错误示例
❌ 错误示例 1:批量导入时使用扩展运算符
javascript
// 批量导入 Excel,可能有上万行数据
const excelData = []
sheets.forEach((sheet) => {
const current = xlsx.utils.sheet_to_json(workbook.Sheets[sheet])
excelData.push(...current) // ❌ 如果数据量大,会栈溢出!
})
正确做法:
javascript
const excelData = []
sheets.forEach((sheet) => {
const current = xlsx.utils.sheet_to_json(workbook.Sheets[sheet])
// ✅ 使用 concat 或循环
excelData = excelData.concat(current)
// 或者
// for (const row of current) excelData.push(row)
})
❌ 错误示例 2:分页加载时使用扩展运算符
javascript
// 分页加载,累计可能有大量数据
const loadMore = async () => {
const newData = await fetchData(offset)
allData.push(...newData) // ❌ 随着数据累积,可能栈溢出
}
正确做法:
javascript
const loadMore = async () => {
const newData = await fetchData(offset)
// ✅ 使用 concat
allData = allData.concat(newData)
// 或者
// for (const item of newData) allData.push(item)
}
最佳实践建议
- 小数组(< 1000) :随意,
push(...arr)最简洁 - 中数组(1000 - 10000) :优先使用
concat(),安全可靠 - 大数组(> 10000) :使用循环
push(),性能最优 - 不确定大小时 :保守选择
concat()或循环push() - 需要保持原数组不变 :使用
concat() - 内存敏感场景 :使用循环
push(),原地修改
总结
数组合并看似简单,但在处理大规模数据时,选择合适的方法至关重要:
- 扩展运算符
push(...arr):适合小数组,代码简洁,但大数组会栈溢出 concat():适合中大数组,安全可靠,但会创建新数组- 循环
push():适合超大数组,性能最优,内存占用最低
记住一个原则:不确定数组大小时,选择更安全的方法 。在生产环境中,concat() 和循环 push() 是更稳妥的选择。
希望这篇文章能帮助你在实际开发中避免栈溢出的坑!如果觉得有用,欢迎点赞收藏~