Skip to content

数组新增方法

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()#

mapflat(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 + map
const 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'(倒数第二个)
// 越界返回 undefined
arr.at(100) // undefined
arr.at(-100) // undefined

对比传统方式:

const arr = [1, 2, 3, 4, 5]
// 传统获取最后一个元素
arr[arr.length - 1] // 5
// 使用 at
arr.at(-1) // 5
// 动态索引时更方便
function getLast(arr, n = 1) {
return arr.at(-n)
}
getLast([1, 2, 3]) // 3
getLast([1, 2, 3], 2) // 2

toReversed()、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()ES2019map + flat(1)
at()ES2022索引访问(支持负数)-
toReversed()ES2023反转数组
toSorted()ES2023排序数组
toSpliced()ES2023删除/插入元素
with()ES2023替换元素

ES2023 的不可变方法非常适合函数式编程和状态管理场景,可以避免意外修改原数组带来的 bug。