【現象】
代碼如下:
var list = [{ n: "a", v: 1 }, { n: "b", v: 1 }, { n: "c", v: 1 }, { n: "d", v: 1 }, { n: "e", v: 1 }, { n: "f", v: 1 }, { n: "g", v: 1 }, { n: "h", v: 1 }, { n: "i", v: 1 }, { n: "j", v: 1 }, { n: "k", v: 1 }, ]; list.sort(function (a, b) { return a.v - b.v; }); for (var i = 0; i < list.length; i++) { console.log(list[i].n); }
很簡單,就是定一個對象數組,再用sort方法按v字段對其排序,這個問題很容易讓人忽略,因為按正常思維都會認為只要所有v是相等的那么結果就跟沒排序之前是一樣的,但是……
結果如下:
====IE11====
====火狐====
====Chrome====
可以看出,IE跟火狐都沒問題,但Chrome卻成了亂序。
經過查閱資料,網上對這個問題似乎沒有多少實際的解決辦法。據說谷歌開發者認為這不是個bug不予解決,因為V8引擎的原因,為了高效排序,稱之為不穩定排序。其實這也不算是一個BUG,不同人會有不同的看法:a:"因為排序依據是相同的就是沒有順序,沒有順序就是亂序,這種結果是正確的";b:"既然排序依據是相同的那就按照原始順序輸出"(這應該是大多數據語言里常規的做法)。網上有牛人說數組超過10條后會調用另一種排序方法(插入排序),10以下用的是快速排序算法,為了提交效率,所以會出現這種情況。
網上有人給出了辦法就是相同的情況下強制產生差異(當順序相同時,讓a比b小):
list.sort(function (a, b) { return a.v - b.v || -1; });
經測試,該方法無效。原因可能是返回值都相同導致。
很苦惱,於是繼續想辦法,既然返回值相同也會出現這個問題,那如何能讓返回值即不相同排序結果還要正確呢?於是經過一番思索,想到了一個值:index,沒錯!就是根據索引來排序,如果順序相同那么就根據比較索引,索引的順序就是排序前的順序,於是代碼改為:
list.sort(function (a, b) { return a.v - b.v || list.indexOf(a)-list.indexOf(b); });
測試結果仍然不行。
有些苦惱,仔細想了一下發現:思路是沒有問題,但是在排序中元素位置是不斷發生變化的,所以indexOf取出的不是原始的索引位置,所以排序仍然不正確。因此按照這個思路去做,在每個元素里加個屬性來保留它原始的索引,然后再按此索引排序,代碼更改后如下:
for (var i = 0; i < list.length; i++) { list[i].oldIndex = i; } list.sort(function (a, b) { return a.v - b.v || a.oldIndex - b.oldIndex; });
測試結果沒問題,跟我們預期的相同!
【解決辦法】
於是正確結果就是:先循環給每個元素增加一個屬性,用來保存它目前的位置,然后再排序中遇到等序時取索引進行排序
例如:
for (var i = 0; i < list.length; i++) { list[i].oldIndex = i; } list.sort(function (a, b) { return a.v - b.v || a.oldIndex - b.oldIndex; });