ES2019 及后续版本继续为数组添加新方法,进一步增强数组处理能力。
flat()#
将嵌套数组”拍平”为一维数组。
基本用法#
// 默认拍平一层;[1, [2, [3]]].flat() // [1, 2, [3]]
// 指定拍平深度;[1, [2, [3]]].flat(2) // [1, 2, 3]
// 完全拍平;[1, [2, [3, [4, [5]]]]].flat(Infinity) // [1, 2, 3, 4, 5]移除空位#
;[1, , 2, , 3].flat() // [1, 2, 3]实现原理#
function flat(arr, depth = 1) { if (depth === 0) return arr.slice() return arr.reduce((acc, item) => { if (Array.isArray(item)) { return acc.concat(flat(item, depth - 1)) } return acc.concat(item) }, [])}flatMap()#
先 map 再 flat(1),只能拍平一层。
基本用法#
// flatMap = map + flat(1)const arr = [1, 2, 3]
arr.map((x) => [x, x * 2]) // [[1, 2], [2, 4], [3, 6]]arr.map((x) => [x, x * 2]).flat() // [1, 2, 2, 4, 3, 6]arr.flatMap((x) => [x, x * 2]) // [1, 2, 2, 4, 3, 6]过滤与映射#
// 传统方式:filter + mapconst nums = [1, 2, 3, 4, 5]nums.filter((n) => n % 2 === 0).map((n) => n * 2) // [4, 8]
// flatMap 可以同时做到nums.flatMap((n) => (n % 2 === 0 ? [n * 2] : [])) // [4, 8]拆分字符串#
const sentences = ['Hello World', 'Foo Bar']
sentences.flatMap((s) => s.split(' '))// ['Hello', 'World', 'Foo', 'Bar']展开嵌套结构#
const users = [ { name: '张三', hobbies: ['读书', '游泳'] }, { name: '李四', hobbies: ['篮球'] },]
// 提取所有爱好users.flatMap((u) => u.hobbies)// ['读书', '游泳', '篮球']
// 生成用户-爱好对users.flatMap((u) => u.hobbies.map((h) => ({ user: u.name, hobby: h })))// [// { user: '张三', hobby: '读书' },// { user: '张三', hobby: '游泳' },// { user: '李四', hobby: '篮球' }// ]at()#
ES2022 新增,支持负索引访问:
const arr = ['a', 'b', 'c', 'd', 'e']
// 正向索引arr.at(0) // 'a'arr.at(2) // 'c'
// 负向索引arr.at(-1) // 'e'(最后一个)arr.at(-2) // 'd'(倒数第二个)
// 越界返回 undefinedarr.at(100) // undefinedarr.at(-100) // undefined对比传统方式:
const arr = [1, 2, 3, 4, 5]
// 传统获取最后一个元素arr[arr.length - 1] // 5
// 使用 atarr.at(-1) // 5
// 动态索引时更方便function getLast(arr, n = 1) { return arr.at(-n)}getLast([1, 2, 3]) // 3getLast([1, 2, 3], 2) // 2toReversed()、toSorted()、toSpliced()、with()#
ES2023 新增的不可变数组方法,返回新数组而不修改原数组。
toReversed()#
const arr = [1, 2, 3]
// 原有的 reverse() 会修改原数组// arr.reverse(); // [3, 2, 1],arr 也变了
// toReversed() 返回新数组const reversed = arr.toReversed()console.log(reversed) // [3, 2, 1]console.log(arr) // [1, 2, 3](原数组不变)toSorted()#
const arr = [3, 1, 2]
// 原有的 sort() 会修改原数组// arr.sort(); // [1, 2, 3],arr 也变了
// toSorted() 返回新数组const sorted = arr.toSorted()console.log(sorted) // [1, 2, 3]console.log(arr) // [3, 1, 2](原数组不变)
// 支持比较函数;[3, 1, 2].toSorted((a, b) => b - a) // [3, 2, 1](降序)
// 对象数组排序const users = [ { name: '张三', age: 30 }, { name: '李四', age: 25 },]users.toSorted((a, b) => a.age - b.age)// [{ name: '李四', age: 25 }, { name: '张三', age: 30 }]toSpliced()#
const arr = [1, 2, 3, 4, 5]
// 原有的 splice() 会修改原数组// arr.splice(2, 1); // [3],arr 变为 [1, 2, 4, 5]
// toSpliced() 返回新数组const spliced = arr.toSpliced(2, 1)console.log(spliced) // [1, 2, 4, 5]console.log(arr) // [1, 2, 3, 4, 5](原数组不变)
// 插入元素;[1, 2, 5].toSpliced(2, 0, 3, 4) // [1, 2, 3, 4, 5]
// 替换元素;[1, 2, 3].toSpliced(1, 1, 'two') // [1, 'two', 3]with()#
替换指定索引的元素,返回新数组:
const arr = [1, 2, 3]
// 传统方式// arr[1] = 20; // 会修改原数组
// with() 返回新数组const updated = arr.with(1, 20)console.log(updated) // [1, 20, 3]console.log(arr) // [1, 2, 3](原数组不变)
// 支持负索引;[1, 2, 3].with(-1, 30) // [1, 2, 30]
// 链式调用;[1, 2, 3].with(0, 10).with(1, 20).with(2, 30)// [10, 20, 30]实战应用#
状态管理(不可变更新)#
const state = { items: [ { id: 1, name: 'A', done: false }, { id: 2, name: 'B', done: false }, { id: 3, name: 'C', done: true }, ],}
// 标记完成function toggleTodo(state, id) { const index = state.items.findIndex((item) => item.id === id) return { ...state, items: state.items.with(index, { ...state.items[index], done: !state.items[index].done, }), }}
// 删除function deleteTodo(state, id) { const index = state.items.findIndex((item) => item.id === id) return { ...state, items: state.items.toSpliced(index, 1), }}
// 排序function sortTodos(state) { return { ...state, items: state.items.toSorted((a, b) => a.name.localeCompare(b.name)), }}数据处理管道#
const data = [ { name: '张三', scores: [ [80, 90], [85, 95], ], }, { name: '李四', scores: [ [70, 75], [80, 85], ], },]
// 计算每个学生的平均分const results = data .map((student) => ({ name: student.name, average: student.scores.flat().reduce((sum, s) => sum + s, 0) / student.scores.flat().length, })) .toSorted((a, b) => b.average - a.average)
// [{ name: '张三', average: 87.5 }, { name: '李四', average: 77.5 }]树形结构展平#
function flattenTree(tree, depth = Infinity) { if (depth === 0) return [tree]
return [ tree, ...(tree.children || []).flatMap((child) => flattenTree(child, depth - 1)), ]}
const tree = { id: 1, children: [{ id: 2, children: [{ id: 4 }] }, { id: 3 }],}
flattenTree(tree).map((n) => n.id) // [1, 2, 4, 3]矩阵转置#
function transpose(matrix) { return matrix[0].map((_, colIndex) => matrix.map((row) => row[colIndex]))}
transpose([ [1, 2, 3], [4, 5, 6],])// [// [1, 4],// [2, 5],// [3, 6]// ]方法汇总#
| 方法 | 版本 | 作用 | 是否修改原数组 |
|---|---|---|---|
| flat() | ES2019 | 数组扁平化 | 否 |
| flatMap() | ES2019 | map + flat(1) | 否 |
| at() | ES2022 | 索引访问(支持负数) | - |
| toReversed() | ES2023 | 反转数组 | 否 |
| toSorted() | ES2023 | 排序数组 | 否 |
| toSpliced() | ES2023 | 删除/插入元素 | 否 |
| with() | ES2023 | 替换元素 | 否 |
ES2023 的不可变方法非常适合函数式编程和状态管理场景,可以避免意外修改原数组带来的 bug。