今天的文章和你們談一談如何用JavaScript進行數組去重,這是一道常見的面試(筆試)題,能夠很好地考察出一我的的邏輯思惟及邊界考慮狀況,但願此文可以幫助你們在解決相似問題時拓寬思路。據我到目前爲止面試的狀況,不多有人能在現場考慮很全,基本上的人都是淺嘗輒止。面試
固然,「使用庫中的一個函數就能去重」並不在本篇文章的討論範圍內,咱們針對的是須要本身寫代碼的場景。考慮到實際狀況,咱們使用ES5(主要就用了indexOf方法,若是是更古老的環境,能夠本身增長這段代碼,或者使用ES5兼容庫es5-sham.js)。算法
咱們先審題:數組,題目中並無說是什麼樣的數組,即數組的組成元素多是字符串、數字、布爾、數組、對象、Null、Undefined。數組
在開始以前咱們先看看這些類型以及他們的值比較關係:函數
接着咱們來看看數組中的indexOf方法:es5
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都是不一樣的引用地址,天然也就找不到索引值啦 。code
好了,迴歸正題,咱們要進行數組去重,那麼先想個大體的思路,好比:對象
1)新建一個空數組,老數組從第一個開始,看看新數組中有沒有,若是沒有就push進入新數組,若是存在就下一個。索引
2)在一個數組裏面從第一個開始,將它後面的元素依次與當前這個比較,若是相等,就把後面的那個元素刪掉,依次往復操做,直到最後一個。接着比較對象變成第二個,重複上述步驟,直到比較對象是最後一個。ip
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);
可是當數組元素爲引用類型時,引用地址不同但在咱們看來是徹底同樣的兩個元素,這個方法是去不掉的。