如何在不改变原始数组的情况下对数组进行排序?

464

假设我想要一个排序函数,该函数返回输入数组的排序副本。 我尝试了以下代码:

function sort(arr) {
  return arr.sort();
}

我用下面的测试代码进行验证,结果证明我的sort方法对数组进行了改变。

var a = [2,3,7,5,3,7,1,3,4];
sort(a);
alert(a);  //alerts "1,2,3,3,3,4,5,7,7"

我也尝试了这种方法。

function sort(arr) {
  return Array.prototype.sort(arr);
}

但它根本不起作用。

有没有简单的方法绕过这个问题,最好不需要手动编写自己的排序算法或将数组的每个元素复制到一个新数组中?


1
创建数组的深拷贝并对其进行排序。 - evanmcdonnal
2
@evanmcdonnal 如果只需要重新排序而不是数组中每个项目的副本,则浅复制可能已经足够了。 - Kekoa
“.sort” 要求“this”值为数组,因此对于最后一个片段要工作,您需要执行“.sort.call(arr)”(尽管这并不能解决您的问题)。 - pimvdb
@Kekoa 是的,这是个很好的观点。如果你只打算改变元素的顺序而不改变元素本身,那么就没有必要消耗更多的内存。 - evanmcdonnal
zzzzBov的方法非常有效!https://dev59.com/w2kw5IYBdhLWcg3w2-XH#9592774 - zimmerbimmer
13个回答

500

这真的很棒。我认为比连接和其他方法更容易理解。 - sktguha
这段代码确实可以工作,但是当我使用Gulp编译JS时,它会返回一个错误:SyntaxError: Unexpected token: punc (.)。 - brassmookie
6
对于那些说它不是有效的JavaScript的人来说...它是完全有效的。如果你在Chrome/Safari/Edge或Firefox中:打开开发控制台,定义一个名为arr的数组,然后粘贴这个表达式以查看结果。 - shangxiao
3
听起来你正在使用一个极为过时的 JavaScript 版本。 - Kloar
1
这个方法获取数组的副本比.slice()更快吗? - S.Serpooshan
显示剩余2条评论

258

只需要复制该数组。有许多方法可以做到这一点:

function sort(arr) {
  return arr.concat().sort();
}

// Or:
return Array.prototype.slice.call(arr).sort(); // For array-like objects

4
这会进行深拷贝吗?也就是说,嵌套的对象和数组也会被复制吗? - Peter Olson
8
不,这是一个浅复制。如果你真的想要进行深度复制,请使用 Stack Overflow 的搜索功能查找现有的优秀答案。 - Rob W
18
Slice现在被报告为明显更快。 - Zander Brown
7
为什么要使用 Array.prototype.slice.call(arr).sort(); 而不是 arr.slice().sort(); ? - Olivier Boissé
3
原文:@OlivierBoissé The prototype call works for array-like objects, too. Not only arrays themselves.翻译:原型调用对于类似数组的对象也适用,不仅仅是数组本身。 - Scindix
显示剩余7条评论

80

请尝试以下方法

function sortCopy(arr) { 
  return arr.slice(0).sort();
}

slice(0) 表达式会创建一个从第 0 个元素开始的数组副本。


56

您可以使用没有参数的切片来复制一个数组:

var foo,
    bar;
foo = [3,1,2];
bar = foo.slice().sort();

这个答案太棒了!我很惊讶JavaScript允许这么大程度的变异。感觉不对。再次感谢。 - user3054109
这不是一次变异,你正在将foo数组浅复制到bar变量中。 - Ali Mert Çakar

21

你也可以这样做

d = [20, 30, 10]
e = Array.from(d)
e.sort()

这样d就不会被改变。

function sorted(arr) {
  temp = Array.from(arr)
  return temp.sort()
}

//Use it like this
x = [20, 10, 100]
console.log(sorted(x))

这个答案很好。 - Leasye

12
更新 - Array.prototype.toSorted() 提案
Array.prototype.toSorted(compareFn) -> Array 是一个新的方法,建议将其添加到 Array.prototype 中,目前处于 stage 3(即将可用)。
该方法将保持目标数组不变,并返回执行更改后的副本。

7

ES2023 数组方法 toSorted()

Array 实例的 toSorted() 方法是 sort() 方法的复制版本。它返回一个按升序排序的新数组。

const arr = [2, 1, 3];
const arrSorted = arr.toSorted();
console.log(arr); //[2, 1, 3]
console.log(arrSorted); //[1, 2, 3]

PS:toSorted()方法几乎被所有浏览器和Node.js 20+版本支持。
请参阅浏览器兼容性


2

如果您想要进行深拷贝(例如,如果您的数组包含对象),可以使用以下方法:

let arrCopy = JSON.parse(JSON.stringify(arr))

然后你可以对arrCopy进行排序,而不改变arr
arrCopy.sort((obj1, obj2) => obj1.id > obj2.id)

请注意:对于非常大的数组,这可能会很慢。

1
在你的第二个例子中,使用“-”而不是“>”也可以正常工作。 - tkit
2
请记住,为了在字符串化后将它们带回来,您的所有项目都应该是可序列化的(例如,日期对象、函数和符号在此方法中存在问题)。 - btargac

1
新的tc39提案已经出现了,它给Array添加了一个toSorted方法,该方法返回数组的副本而不修改原始数组。
例如:
const sequence = [3, 2, 1];
sequence.toSorted(); // => [1, 2, 3]
sequence; // => [3, 2, 1]

由于它目前处于第三阶段,很快就会在浏览器引擎中实现,但同时可以使用此处core-js中的polyfill。


1

尝试使用以下方法对数字进行排序。此方法不会改变原始数组。

function sort(arr) {
  return arr.slice(0).sort((a,b) => a-b);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接