先记住这个答案
会。sort() 是原地排序,直接把原数组里的元素重新排列,返回值也是原数组本身。所以 const sorted = arr.sort() 之后,sorted 和 arr 是同一个数组。想保留原顺序,就先复制再排:[...arr].sort() 或 arr.slice().sort(),支持 ES2023 的环境可以直接用 arr.toSorted()。要注意这些都是浅拷贝,数组元素是对象时,新旧数组共享同一批对象。
sort()原地排序,返回值就是原数组- 保留原数组:先复制再排,或直接用
toSorted() - 复制只复制外层,对象元素仍然是共享的
一个典型场景:列表切换排序方式
工单后台的 tickets 数组按提交时间排好了。用户点「按优先级排序」时只想改变显示顺序,再点「默认排序」要能恢复。如果直接写 tickets.sort(...),原来的时间顺序就被覆盖了,没法恢复。正确做法是复制一份再排,渲染这份副本。
const tickets = [{ id: 'A', priority: 1 }, { id: 'B', priority: 3 }]
const view = [...tickets].sort((a, b) => b.priority - a.priority)
console.log(tickets.map(t => t.id).join(',')) // 原数组顺序不变
console.log(view.map(t => t.id).join(',')) // 副本按优先级排
console.log(view === tickets) // 两个不同的数组
console.log(view[0] === tickets[1]) // 但里面是同一个对象
view[0].priority = 9
console.log(tickets[1].priority) // 改副本里的对象,原数组也变了查看输出与解释
A,B
B,A
false
true
9外层数组是新的,所以两边的顺序互不影响,恢复默认排序时直接渲染 tickets 就行。但里面的工单对象还是原来那两个,通过 view 改了优先级,tickets 里读到的也是 9。复制数组只保护了顺序,没保护对象内容。
如果连对象也要隔离,就要深拷贝,比如 structuredClone(tickets)。数据量大时深拷贝有成本,大多数展示场景只需要保护顺序,浅拷贝就够了。
三种复制写法的细微差别
slice()、展开运算符和 toSorted() 在普通数组上效果一样,差别只在稀疏数组上。稀疏数组就是中间有空位的数组,比如 [3, , 1],索引 1 上什么都没有,连 undefined 都不是。
- 空位的处理
slice()会原样保留空位,排序后空位被挪到最后,仍然是空位。展开运算符和toSorted()会把空位读成undefined,结果末尾是一个真实存在的undefined。 平时的业务数据很少是稀疏的,这个差别知道就行。真正要记住的是:slice()和展开是先复制再调sort(),而toSorted()一步完成,老环境不支持toSorted()时,用前两种兜底。
容易答错的地方
- 以为
sort()和map()一样会返回新数组 map()和filter()返回新数组,sort()不是。const b = a.sort()之后b === a为true,改b就是在改a。同样原地修改的还有reverse()和splice(),它们对应的不改原数组版本是toReversed()和toSpliced()。- 以为复制一层就完全不会互相影响
[...arr]只让两个数组的顺序互不影响。元素是对象时,任何一边改对象字段,另一边都能看到。需要完全隔离,要么深拷贝,要么用不可变的方式更新对象,比如{ ...item, priority: 9 }生成新对象。
面试官还会怎么问?
React 里为什么不能直接对 state 数组调用 sort()?
sort() 改的是原数组,引用没变。React 用 Object.is 比较新旧 state,引用相同就认为没变化,可能不会重新渲染,而且原 state 已经被改坏了。要写成 setList([...list].sort(compare)) 或 setList(list.toSorted(compare))。
先复制再排序会影响性能吗?
复制是 O(n),排序本身是 O(n log n),复制那一步通常不是瓶颈,额外多占一份数组的内存。如果同一份排序结果要反复使用,缓存起来,不要每次渲染都重新复制加排序。
比较器里可以修改元素吗?
不应该。比较器会被调用很多次,调用顺序由引擎决定,在里面改元素会让结果难以预测,还会改到原数据。比较器只读字段、返回正负数就好。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。