文章出自個人博客https://knightyun.github.io/2020/01/12/js-array-sort,轉載請申明
提到 JavaScript 中對數組進行排序操作,可能首先想到的就是 Array.prototype.sort()
這個函數,比如以下場景就比較常見:
var arr = [3, 1, 2];
console.log(arr.sort());
// [1, 2, 3]
console.log(arr); // sort() 函數會修改原數組
// [1, 2, 3]
arr = ['c', 'b','B', 'a','A'];
arr.sort();
console.log(arr);
// ["A", "B", "a", "b", "c"]
和預想的一樣,sort()
函數默認將數組元素升序排列,但是不要被上面的數字數組的排序結果迷惑,該函數並不是按照數字遞增的方式排列的,而是按照元素的 ASCII 碼或者 Unicode 碼進行排序,比如字符 a
對應的 ASCII 碼要比字符 b
的小,所以 a
排在 b
前面,同樣字符 A
的比字符 a
的小,所以大寫字母 A
會排在小寫字母 a
前面;考慮以下情景:
var arr = [1, 2, 11, 12];
arr.sort();
console.log(arr);
// [1, 11, 12, 2]
是不是有些和預想的不一樣,這也驗證了之前所說,並不是按照數字遞增在排序,而是把數組中的數字類型的元素轉換成字符,在拆分字符比較單個字符對應的字符碼的大小;
比較函數
那麼問題就來了,要按照數字遞增方式排序,該怎麼操作呢?其實這種情況早就被 .sort()
函數考慮到了,只是可能被大家忽略了,就是 .sort()
函數還能接受一個參數,叫做 compareFunction
,顧名思義,就是 比較函數,由於該參數是一個函數,所以該函數又能接受兩個參數,即比較的值,所以最終就是 .sort(compareFunction(a, b))
;
關於這個 比較函數,存在如下規則:
- 如果
compareFunction(a, b)
返回值小於 0 ,那麼a
會被排列到b
之前; - 如果
compareFunction(a, b)
返回值等於 0 ,那麼a
和b
的相對位置不變;- 備註: ECMAScript 標準並不保證這一行爲,而且也不是所有瀏覽器都會遵守(例如 Mozilla 在 2003 年之前的版本);
- 如果
compareFunction(a, b)
返回值大於 0 ,那麼b
會被排列到a
之前;
compareFunction(a, b)
必須總是對相同的輸入返回相同的比較結果,否則排序的結果將是不確定的。
在使用它之前,先來看看函數裏面的參數 a, b
是如何對應數組元素的:
var arr = [2, 1, 4, 3];
arr.sort(function(a, b) {
console.log(a, b);
})
// 1 2
// 4 1
// 3 4
可以發現,由於這裏的比較函數沒有返回值,所以對數組就沒有排序操作,而每一次遍歷中,第二個參數 b
對應前一個元素,第一個參數 a
對應後一個元素;當然該函數的具體排序方法就不得而知並且因 JS 引擎而異了;
升序
對數組按照升序方式排序,即小的元素排在前面,大的元素排在後面,假設比較函數當前遍歷的元素對爲 (2, 1)
,則 a = 1, b = 2
,要想升序就要 a
排到 b
的前面,對應上面的規則,就是需要比較函數的返回值小於 0,由於當前 a - b < 0
;所以直接返回一個 a - b
就行了,代碼如下:
var arr = [2, 1, 3, 11, 12, 11]
arr.sort(function(a, b) {
return a - b;
})
console.log(arr);
// [1, 2, 3, 11, 11, 12]
針對上面的代碼再來分析下,在每一次遍歷比較的兩個元素中:
- 如果後一個元素比前一個元素小,即
a - b < 0
,按照規則就是a
要排到b
的前面,也就是這兩個元素會交換,小的在前,大的在後; - 如果後一個元素比前一個元素大,即
a - b > 0
,按照規則就是b
要排到a
的前面,由於b
本來就在a
的前面,所以兩元素位置不變; - 如果後一個元素與前一個元素相同,即
a - b = 0
,按照規則就是a
和b
的位置不變,兩元素位置同樣不變;
最後,數組就變成升序的了;
降序
原理和升序類似,只是思路反過來了,代碼如下:
var arr = [2, 1, 3, 11, 12, 11]
arr.sort(function(a, b) {
return b - a;
})
console.log(arr);
// [12, 11, 11, 3, 2, 1]
同樣來分析一下,每一次遍歷中:
- 如果前一個元素比後一個元素小,即
b - a < 0
,按照規則就是a
要排到b
的前面,也就是這兩個元素會交換,大的在前,小的在後; - 如果前一個元素比後一個元素大,即
b - a > 0
,按照規則就是b
要排到a
的前面,由於b
本來就在a
的前面,所以兩元素位置不變; - 如果前一個元素與後一個元素相同,即
b - a = 0
,按照規則就是a
和b
的位置不變,兩元素位置同樣不變;
最後,數組也就變成降序了;
反序
這是排序函數一個另一個應用,作用相當於 .reverse()
函數,即讓數組中的元素順序顛倒,實現也很簡單,就是利用規則,讓每次比較函數的返回值小於 0 就行了,例如:
var arr = [2, 1, 4, 3];
arr.sort(function(a, b) {
return -1
});
console.log(arr);
// [3, 4, 1, 2]
亂序
這也算是一個比較實用的用途了,即將數組中元素的位置和順序打亂,增加隨機性,實現也簡單,即利用規則,讓比較函數的返回值隨機爲 > 0, < 0, = 0
這三種情況之一,使得元素是否交換位置具有隨機性,也就實現了順序的打亂,實現代碼如下:
var arr = [1, 2, 3, 4, 5];
arr.sort((a, b) => {
return 0.5 - Math.random();
});
console.log(arr);
// [4, 3, 2, 1, 5]
arr.sort((a, b) => {
return 0.5 - Math.random();
});
console.log(arr);
// [5, 3, 1, 2, 4]