在掘金上每過一段時間就會出現一次題材類似的討論,例如面試,手寫函數,而此題材也不例外——數組去重。我看到過論壇上出現過的一次次的數組去重的文章,可是我的以爲可能都不太完善,因此,我指望嘗試對 JS 數組去重進行一次 「系統性」 的總結。html
該用例包含了全部簡單基本類型以及對象,基本能夠涵蓋全部常見數據的可能es6
let a = {};
let b = { a: 1 };
let testArr = [1, 1, "1", "1", "a", "a", {}, {}, { a: 1 }, a, a, b, [], [], [1], undefined, undefined, null, null, NaN, NaN];
複製代碼
如下的代碼以 lodash 中默認去重函數的去重結果爲標準,lodash uniq
函數去重結果以下面試
// lodash@4.17.15
_.uniq(testArr);
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null, NaN];
複製代碼
對於 JS 數組去重來講,其實萬變不離其中,我簡單的總結了如下 4 種去重類型,而圍繞着各類類型,因爲 JS 豐富的函數以及工具能力,能夠有多種實現方法,無論是採用 forEach,reduce,filter
等來循環並拼裝新數組,仍是用最基礎的 for
循環以及 push
來循環並組裝新數組,暫時均可以概括到如下的 4 個類型數組
該方法是將數組的值取出與其餘值比較並修改數組函數
取出一個元素,將其與其後全部元素比較,若在其後發現相等元素,則將後者刪掉工具
function uniq(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j]) {
arr.splice(j, 1);
j--;
}
}
}
return arr;
}
// 運行結果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null, NaN, NaN]
// 與 lodash 結果相比 NaN 沒有去掉
複製代碼
因爲 NaN === NaN
等於 false
,因此重複的 NaN 並無被去掉性能
先對數組內元素進行排序,再對數組內相鄰的元素兩兩之間進行比較,經測試,該方法受限於 sort
的排序能力,因此若數組不存在比較複雜的對象(複雜對象難以排序),則可嘗試此方法學習
function uniq(arr) {
arr.sort();
for (let i = 0; i < arr.length - 1; i++) {
arr[i] === arr[i + 1] && arr.splice(i + 1, 1) && i--;
}
return arr;
}
// 運行結果
//[[], [], 1, "1", [1], NaN, NaN, {}, {}, { a: 1 }, {}, { a: 1 }, "a", null, undefined];
// 與 lodash 結果相比 NaN 沒有去掉,且對象的去重出現問題
複製代碼
一樣因爲 NaN === NaN
等於 false
,因此重複的 NaN 並無被去掉,而且因爲 sort
沒有將對象很好的排序,在對象部分,會出現一些去重失效測試
該類型即針對每一個數組元素進行一次查找其在數組內的第一次出現的位置,若第一次出現的位置等於該元素此時的索引,即收集此元素ui
以 indexOf
來查找元素在數組內第一次出現的位置,若位置等於當前元素的位置,則收集
function uniq(arr) {
let res = [];
for (let i = 0; i < arr.length; i++) {
if (arr.indexOf(arr[i]) === i) {
res.push(arr[i]);
}
}
return res;
}
// 運行結果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null];
// 與 lodash 結果相比 少了 NaN
複製代碼
indexOf
採用與 ===
相同的值相等判斷規則,因此在數組內沒有元素與 NaN 相等,包括它本身,因此 NaN 一個都不會被留下
以 findIndex
方法來查找元素在數組內第一次出現的位置
function uniq(arr) {
let res = [];
for (let i = 0; i < arr.length; i++) {
if (arr.findIndex(item => item === arr[i]) === i) {
res.push(arr[i]);
}
}
return res;
}
// 運行結果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null];
// 與 lodash 結果相比 少了 NaN
複製代碼
結果原理和 indexOf
相同,由於用了 ===
的規則來判斷元素是否相等,但此方法至關於雙層 for
循環
該方法基本依託 includes
方法來判斷對應元素是否在新數組內存在,若不存在則收集
function uniq(arr) {
let res = [];
for (let i = 0; i < arr.length; i++) {
if (!res.includes(arr[i])) {
res.push(arr[i]);
}
}
return res;
}
// 運行結果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null, NaN];
// 與 lodash 結果相同
複製代碼
includes
方法採用 SameValueZero 判斷規則,因此能夠判斷出並去重 NaN
該方案依託於數據類型的不重複特性,以達到去重效果
Map 類型的數據能夠像 Object 同樣,在設定元素鍵值對的時候能夠保證鍵的惟一,而且將鍵的類型拓展到了基本全部元素,包括對象,在設定好一個惟一鍵的 Map 數據類型後,再用其自帶的 Map.prototype.keys()
方法取到相應的鍵類數組,最後將類數組進行一次轉化便可
function uniq(arr) {
let map = new Map();
for (let i = 0; i < arr.length; i++) {
!map.has(arr[i]) && map.set(arr[i], true);
}
return [...map.keys()];
}
// 運行結果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null, NaN];
// 與 lodash 結果相同
複製代碼
與 Map 相似,運用數據類型的特性完成去重,這個方法也是最熱門的方法
function uniq(arr) {
return [...new Set(arr)];
}
// 運行結果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null, NaN];
// 與 lodash 結果相同
複製代碼
這裏簡單(不嚴謹)的將剛纔使用的測試例子拼接 100 萬次
let a = {};
let b = { a: 1 };
let testArr = [1, 1, "1", "1", "a", "a", {}, {}, { a: 1 }, a, a, b, [], [], [1], undefined, undefined, null, null, NaN, NaN];
let arr = [];
for (let i = 0; i < 1000000; i++) {
arr.push(...testArr);
}
uniq(arr);
複製代碼
先說結果,在簡單的測試用例大概 2000 萬條數據下,indexOf
的方案速度相對最快
我以前還寫過兩個小工具,但願能夠幫助到你們,一塊兒學習一塊兒進步。