如何用JavaScript進行陣列去重
今天的文章和大家談一談如何用JavaScript進行陣列去重,這是一道常見的面試(筆試)題,可以很好地考察出一個人的邏輯思維及邊界考慮情況,希望此文能夠幫助大家在解決類似問題時拓寬思路。據我到目前為止面試的情況,很少有人能在現場考慮很全,基本上的人都是淺嘗輒止。
當然,“使用庫中的一個函式就能去重”並不在本篇文章的討論範圍內,我們針對的是需要自己寫程式碼的場景。考慮到實際情況,我們使用ES5(主要就用了indexOf方法,如果是更古老的環境,可以自己增加這段程式碼,或者使用ES5相容庫es5-sham.js)。
我們先審題:陣列,題目中並沒有說是什麼樣的陣列,即陣列的組成元素可能是字串、數字、布林、陣列、物件、Null、Undefined。
在開始之前我們先看看這些型別以及他們的值比較關係:
接著我們來看看陣列中的indexOf方法:
var gtArray = [66], gtObject = { id: 1 }, gtTestArr = ["1", 1, true, [66], gtArray, { id: 1 }, gtObject, null, undefined]; gtTestArr.indexOf("1"); // 0 gtTestArr.indexOf(1); // 1 gtTestArr.indexOf(true); // 2 gtTestArr.indexOf([66]); // -1 gtTestArr.indexOf(gtArray); // 4 gtTestArr.indexOf({ id: 1 }); // -1 gtTestArr.indexOf(gtObject); // 6 gtTestArr.indexOf(null); // 7 gtTestArr.indexOf(undefined); // 8
從上述效果中看我們可以得出結論:indexOf 可以幫我們找到一個數組中某個元素(若該元素為陣列或者物件,則為該引用的地址值)對應的索引值,在人腦“看”來相同的[66] 和 gtArray,實際上除了都用gtArray表示的部分是一樣的,其他的 [66]之間以及gtArray都是不同的引用地址,自然也就找不到索引值啦 。
好了,迴歸正題,我們要進行陣列去重,那麼先想個大致的思路,比如:
1)新建一個空陣列,老陣列從第一個開始,看看新陣列中有沒有,如果沒有就push進入新陣列,如果存在就下一個。
2)在一個數組裡面從第一個開始,將它後面的元素依次與當前這個比較,如果相等,就把後面的那個元素刪掉,依次往復操作,直到最後一個。接著比較物件變成第二個,重複上述步驟,直到比較物件是最後一個。
3)and so on
當然每個思路有不同的演算法,對於一種判斷描述也可以有不同的實現方式(如下面的相等),比如用 map,用下標等。不同方式可能也會有不同的侷限性或者前置條件。
好,現在我們界定一下什麼是相等,簡單的 1 與 1 肯定是相等的,而1 與 “1”是不等的,對於引用型別我們可以分為幾種模式(級別):
1)僅引用地址一樣才算相等。
2)引用地址可以不一樣,但對應的陣列(物件)所擁有的元素(鍵值對)一模一樣就算相等。 即在我們看來,這兩個資料寫出來,看上去就是一樣的。
3)對於是非陣列的物件,針對幾個key的值是一樣的情況,我們將其認定是一樣的。比如 { id: 1, name: ”張三” } 和 { id: 1, name: ”李四” } 在只考慮 id 欄位時他們就是一樣的。當然這種型別是我們人為賦予的模式。
好了,準備工作已做好,我們開始碼程式碼吧。
按照思路1,相等的模型取第二種,直接上程式碼如下:
function gtUniqueArr(arr) {
var i,
newArr = [];
function isExist(_item, _arr) {
var k,
find = false;
if (typeof _item !== "object"
|| _item === null) {
return _arr.indexOf(_item) > -1;
}
for (k in _arr) {
if (_arr.hasOwnProperty(k)) {
find = isEqual(_item, _arr[k]);
if (find) {
break;
}
}
}
return find;
};
function isEqual(_a, _b) {
var k,
keysA,
keysB,
equal = true;
if (typeof _a !== "object" ||
_a === null ||
typeof _b !== "object" || _b === null) { // 有非引用型別(陣列與物件)或者有NULL型別時直接判斷
return _a === _b;
}
// _a _b 不同為陣列或者物件時 直接認為不同,否則長得像陣列的物件也會互判相等
if (_a instanceof Array !== _b
instanceof Array) {
return false;
}
// 同為物件或者陣列
keysA = Object.keys(_a);
keysB = Object.keys(_b);
if (keysA.length !== keysB.length) { //
元素量不同肯定就不是一樣了啊
return false;
}
// 其實也可以先判斷下引用地址是否一樣,一樣肯定就相等啦
for (k in _a) {
if (_a.hasOwnProperty(k)) {
equal = isEqual(_a[k], _b[k]);
if (!equal) {
break;
}
}
}
return equal;
};
if (arr && arr.length) {
for (i = 0; i < arr.length; i++) {
if (!isExist(arr[i], newArr)) {
newArr.push(arr[i]);
}
}
}
return newArr;
}
我們可以把isExist,isEqual提取成公共函式,按照思路2,相等型別依然為第二種,上程式碼:
function gtUniqueArr(arr) {
var i, j;
if (arr && arr.length) {
for (i = 0; i < arr.length; i++) {
for (j = i + 1; j < arr.length;
j++) {
if (isEqual(arr[i], arr[j])) {
arr.splice(j, 1);
j--; // 復原因陣列刪除導致的遺漏了的元素指向
}
}
}
}
return arr;
}
當然,要採取不同的相等模式,只要改變 isEqual 函式即可,此處其他兩種相等模式(或者你還有其他假設的相等模式)訴求相對較少,此處便不再展開敘述了(模式1,直接用===比較兩者即可;模式3,用===檢測要求的欄位的值是否一樣)。
當我們的環境是ES6時,一般的去重標準可以使用 set 來做:
var rs = new Set(arr);
但是當陣列元素為引用型別時,引用地址不一樣但在我們看來是完全一樣的兩個元素,這個方法是去不掉的。