前端面试 P005:数组方法、可变性与回调副作用
Showing the original in Chinese (China)
来源:方格前端讲面试 · 5.数组常用方法背后的面试点,17 分 56 秒。
回看定位:02:55 map 与 filter · 05:34 reduce · 15:43 slice 与 splice
先看返回值,再看是否修改原数组
| 方法 | 主要返回值 | 方法本身是否原地修改数组 |
|---|---|---|
| map | 映射后的新数组 | 否 |
| filter | 满足条件的新数组 | 否 |
| forEach | undefined | 不直接负责替换数组内容,但回调可以修改数据 |
| reduce | 最终累加器值,类型不限 | 不要求修改原数组,但回调可以修改数据 |
| slice(start, end) | 选定范围的浅拷贝,含 start、不含 end | 否 |
| splice(start, deleteCount, ...items) | 被删除元素组成的数组 | 是 |
| push、pop、shift、unshift、sort、reverse | 各有返回值 | 是 |
map 适合转换,filter 适合筛选,forEach 适合只执行操作,reduce 适合把元素归并成一个结果。结果也可以是对象或数组,“一个结果”不等于“一个数字”。
const numbers = [1, 2, 3, 4];
console.log(numbers.map(n => n * 2)); // [2, 4, 6, 8]
console.log(numbers.filter(n => n % 2 === 0)); // [2, 4]
console.log(numbers.reduce((sum, n) => sum + n, 0)); // 10
console.log(numbers); // [1, 2, 3, 4]
reduce 的初始值与空数组
给出 initialValue 时,从第一个存在的元素开始回调;没有初始值时,用第一个存在的元素初始化累加器,从下一个存在的元素开始。没有可用元素且没有初始值时抛 TypeError。
console.log([].reduce((sum, n) => sum + n, 0)); // 0
try {
[].reduce((sum, n) => sum + n);
} catch (error) {
console.log(error.name); // TypeError
}
“第一个存在的元素”比简单背成索引 0 更准确,因为稀疏数组可能有空位。map、filter、forEach、reduce 通常跳过空位;map 会保留对应的空位结构,filter 返回筛选后的连续结果。
slice 与 splice 的实际差别
const months = ['Jan', 'Mar', 'Apr'];
const part = months.slice(0, 2);
const removed = months.splice(1, 1, 'Feb');
console.log(part); // ['Jan', 'Mar']
console.log(removed); // ['Mar']
console.log(months); // ['Jan', 'Feb', 'Apr']
对现代环境,也可以考虑 toSpliced、toSorted、toReversed,以返回新数组的方式替代对应的原地操作。
对视频“安全方法”的边界补充
视频多次把 map、filter、reduce 描述成不会影响原数据的方法。需要区分“方法本身不原地改数组”和“整个调用没有副作用”:回调仍可能修改原数组,或修改共享的元素对象。
const rows = [{ count: 1 }];
const copy = rows.map(row => {
row.count++;
return row;
});
console.log(rows[0].count); // 2
console.log(copy[0] === rows[0]); // true
React 更新嵌套状态时,通常需要复制被修改路径上的对象,例如 rows.map(row => ({ ...row, count: row.count + 1 }))。只是新建外层数组并不自动深拷贝元素,见 前端面试 P004:浅拷贝、深拷贝与 structuredClone。
另一个常见补充:forEach(async () => ...) 不会自动等待回调返回的 Promise。需要并行等待时用 Promise.all 配合 map,需要串行时用 for...of 与 await。
回答要点
先说明返回值、是否原地修改、是否浅拷贝,再解释 reduce 初始值和回调副作用;不要把方法名直接当成“绝对安全”的保证。