学完这篇,你能分清哪些数组方法会改原数组、哪些不会,并在遍历、筛选、转换、排序四类场景里选对方法、不踩坑。
第一步:先分清「改原数组」和「不改原数组」
这是最容易出错的地方,先记住一条:会改原数组的方法有 push/pop/shift/unshift/splice/sort/reverse/fill/copyWithin;返回新数组、原数组不动的有 map/filter/slice/concat/flat/flatMap/toSorted/toReversed。
const a = [3, 1, 2];
const b = a.sort(); // 排序会改原数组
console.log(a); // [1, 2, 3] a 也变了
console.log(a === b); // true,同一个引用
const c = a.toSorted(); // ES2023,返回新数组,a 不变
注意:在 React/Vue 的响应式数据上直接 sort()、reverse()、splice(),会绕过框架的更新检测(或引起意外刷新),优先用 toSorted/toReversed/toSpliced 或先 [...arr] 复制一份。
第二步:遍历与筛选
遍历三兄弟,用途完全不同:
const nums = [1, 2, 3, 4];
// forEach:只做副作用,返回值永远是 undefined
nums.forEach((v, i) => console.log(i, v));
// map:一对一映射,长度不变
const doubled = nums.map(v => v * 2); // [2, 4, 6, 8]
// filter:按条件留下,长度可变
const even = nums.filter(v => v % 2 === 0); // [2, 4]
查找与判断:
nums.find(v => v > 2); // 3 返回第一个匹配的元素
nums.findIndex(v => v > 2); // 2 返回下标,找不到是 -1
nums.findLast(v => v > 2); // 4 从后往前找
nums.some(v => v > 3); // true 有一个满足就 true
nums.every(v => v > 0); // true 全部满足才 true
nums.includes(3); // true 只适合原始值
注意:forEach 里 return 不能中断循环,break 直接报语法错误。想中途跳出,用 for...of 或 some(返回 true 即停)。
累加成一句话用 reduce:
const total = nums.reduce((sum, v) => sum + v, 0); // 10
注意:reduce 不传初始值时,空数组会抛 TypeError,且第一个元素被当作初始值。累加、累乘、分组统计都建议显式传初始值 0、''、{}。
第三步:转换
扁平化:
[[1, 2], [3, [4]]].flat(); // [1, 2, 3, [4]] 默认拍平一层
[[1, 2], [3, [4]]].flat(Infinity); // [1, 2, 3, 4]
[1, 2].flatMap(v => [v, v * 10]); // [1, 10, 2, 20] map 后拍平一层
数组与字符串互转:
[1, 2, 3].join('-'); // "1-2-3"
'1,2,3'.split(','); // ["1", "2", "3"] 注意是字符串数组
'1,2,3'.split(',').map(Number); // [1, 2, 3] 转数字
类数组与对象互转:
Array.from('abc'); // ['a', 'b', 'c']
Array.from({ length: 3 }, (_, i) => i); // [0, 1, 2]
Object.entries({ a: 1, b: 2 }); // [['a', 1], ['b', 2]]
Object.keys({ a: 1, b: 2 }); // ['a', 'b']
注意:Array.from 只接受「有 length 或可迭代」的对象,普通对象要先用 Object.entries/keys/values 转。
第四步:排序
sort 默认按字符串比较,这是个经典坑:
[10, 9, 1].sort(); // [1, 10, 9] 按字典序
[10, 9, 1].sort((a, b) => a - b); // [1, 9, 10] 数字升序
[10, 9, 1].sort((a, b) => b - a); // [10, 9, 1] 数字降序
按对象字段排序:
const users = [{ n: 'A', age: 30 }, { n: 'B', age: 20 }];
users.sort((a, b) => a.age - b.age);
多字段排序:用 || 串联,前一个条件相等时再比下一个。
users.sort((a, b) => a.age - b.age || a.n.localeCompare(b.n));
中文按拼音排序用 localeCompare:
['张三', '李四', '王五'].sort((a, b) => a.localeCompare(b, 'zh'));
注意:比较函数必须返回数字,返回布尔值(如 a.age > b.age)在部分引擎上结果不可靠。另外 sort 是原地排序并且会修改引用指向的数组,需要保留原顺序就先 const copy = [...arr]。
第五步:增删改查的几个常用招式
const arr = [1, 2, 3, 4, 5];
arr.slice(1, 3); // [2, 3] 截取,不改原数组
arr.splice(1, 2); // 返回 [2, 3],原数组变成 [1, 4, 5]
arr.at(-1); // 5 取最后一个,比 arr[arr.length-1] 短
arr.concat([6], [7]); // [1, 4, 5, 6, 7]
[...new Set([1, 1, 2])];// [1, 2] 去重
Array.isArray(arr); // true
注意:slice 和 splice 只差一个字母,前者是「切片」,后者是「挖掉并返回」,写错一个字母就是线上事故,建议只记 slice 用于取值、splice 只在确实要删元素时用。
小结
- 先判断方法是否改原数组:
sort/reverse/splice/push 会改,map/filter/slice/concat/toSorted 不改。
forEach 只做副作用,不能 break;要提前退出用 for...of 或 some。
map 一对一、filter 按条件留、reduce 汇总成任意形态,三者是数据处理的主力。
sort 默认按字符串比较,数字排序一定写 (a, b) => a - b,中文用 localeCompare。
- 需要保留原数组时先
[...arr] 复制,别在响应式状态上直接调用会改原数组的方法。