JavaScript數組方法之數組去重方法

工做過程當中常常會用到數組去重,用到的時候每每一時想不到好方法,因此這裏來總結一下去重方法。
使用es6去重代碼很簡單,並且ES6已經至關普及了。因此先來介紹一下es6中的方法。es6

1.ES6中Map結構方法

function unique (arr) {
  const seen = new Map()
  return arr.filter((a) => !seen.has(a) && seen.set(a, 1))
}
let arr = [1, 2, 1, 3, '1', NaN, NaN, null, null, undefined, undefined, {}, {}];
console.log(uniMap(arr));  //[ 1, 2, 3, '1', NaN, null, undefined, {}, {} ]

Map是es6中新增的數據結構,它相似於對象,也是鍵值對的集合,可是「鍵」的範圍不限於字符串,各類類型的值(包括對象)均可以看成鍵。也就是說,Object 結構提供了「字符串—值」的對應,Map 結構提供了「值—值」的對應,是一種更完善的 Hash 結構實現。
Map的has方法用於判斷map是否含有該鍵。setget 方法分別爲添加成員方法和獲得鍵值方法。
上述方法一方面利用了map的has和set方法,一方面利用了數組的 filter方法,返回結果爲真的元素組成的數組。
注意
Map 的鍵其實是跟內存地址綁定的,只要內存地址不同,就視爲兩個鍵。這句話很差理解的話,能夠這樣說若是 Map 的鍵是一個簡單類型的值(數字、字符串、布爾值),則只要兩個值嚴格相等,Map 將其視爲一個鍵,好比0和-0就是一個鍵,布爾值true和字符串true則是兩個不一樣的鍵, 對象是不一樣的鍵;另外,undefined和null也是兩個不一樣的鍵。雖然NaN不嚴格相等於自身,但 Map 將其視爲同一個鍵算法

2. ES6中數組Set結構方法

function uniMap (arr) {
  return [...new Set(arr)];
}
let arr = [1, 2, 1, 3, '1', NaN, NaN, null, null, undefined, undefined, {}, {}];
console.log(uniMap(arr));  // [ 1, 2, 3, '1', NaN, null, undefined, {}, {} ]

Set 也是ES6 提供的新的數據結構。它相似於數組,可是成員的值都是惟一的,沒有重複的值。
Set 自己是一個構造函數,用來生成Set數據結構,它也接受一個數組或具備iterator接口的數據結構做爲參數初始化。上述代碼中就是利用了這個特性來實現對數組的去重。
Set 具備add方法來添加某個值,返回set結構自己。所以,利用add方法也能夠實現數組的去重。例如:數組

const s = new Set();
function uniMap(arr)  {
    arr.forEach( item => s.add(item));
    return [...s];  // [ 1, 2, 3, '1' ]
}
// main
let arr = [1, 2, 1, 3, '1', 2, 2, 2];
console.log(uniMap(arr));

Array.from也是ES6中的新方法能夠將 Set 結構轉爲數組。這就引出第三種使用set的數組去重方法:數據結構

// set3
function uniMap(arr) {
    return Array.from(new Set(arr));
}
let arr = [1, 2, 1, 3, '1', NaN, NaN, null, null, undefined, undefined, {}, {}];
console.log(uniMap(arr));  // [ 1, 2, 3, '1', NaN, null, undefined, {}, {} ]

注意
Set 內部判斷兩個值是否不一樣,使用的算法叫作「Same-value-zero equality」,它相似於精確相等運算符(===),因此上述結果中 1 和 '1' 認爲是不相同的,都被保留下來,主要的區別是NaN等於自身,而精確相等運算符認爲NaN不等於自身。另外,兩個對象老是不相等的。函數

3. 基本雙重循環

function uniMap(arr) {
    let res = [];
    for(let i = 0, arrLen = arr.length; i < arrLen; i += 1) {
        let j = 0, resLen = res.length;
        for(; j < resLen; j +=1) {
            if(arr[i] === res[j]) {
                break;
            }
        }
        if( j === resLen) {
            res.push(arr[i]);
        }
    }
    return res;  
}
// main
let arr = [1, 2, 1, 3, '1', 2, 2, 2];
console.log(uniMap(arr)); // [ 1, 2, 3, '1' ]
arr = [1, 2, 1, 3, '1', NaN, NaN, null, null, undefined, undefined, {}, {}];
console.log(uniMap(arr)); //[ 1, 2, 3, '1', NaN, NaN, null, undefined, {}, {} ]

咱們使用循環嵌套,最外層循環 array,裏面循環 res,若是 array[i] 的值跟 res[j] 的值相等,就跳出循環,若是都不等於,說明元素是惟一的,這時候 j 的值就會等於 res 的長度,根據這個特色進行判斷,將值添加進 res。
這個是最基本的方法,可是第一次寫還真犯了錯,沒法去重。代碼是這樣的:優化

function uniMap(arr) {
    let res = [];
    for(let i = 0, arrLen = arr.length; i < arrLen; i += 1) {
        let j = 0, resLen = res.length;
        for(; j < resLen; j +=1) {
            if(arr[i] === res[j]) {
                break;
            }
            res.push(arr[i]);
        }
    }
    return res;   // []
}
// main
let arr = [1, 2, 1, 3, '1', 2, 2, 2];
console.log(uniMap(arr));

和上面代碼相比,就是res.push(arr[i]);放在了內循環裏,少了 j === resLen的判斷,就獲得了空數組。緣由是 初始的時候res.length = 0,不會進到內循環,因此res始終爲空。果真眼高手低啊~code

4. indexOf方法優化雙重循環中的內部循環

// 雙重循環2 
function uniMap(arr) {
    let res = [];
    for(let i = 0, arrLen = arr.length; i < arrLen; i += 1) {
        if( res.indexOf(arr[i]) === -1) {
            res.push(arr[i]);
        }
    }
    return res;    
}
// main
let arr = [1, 2, 1, 3, '1', 2, 2, 2];
console.log(uniMap(arr)); // [ 1, 2, 3, '1' ]
arr = [1, 2, 1, 3, '1', NaN, NaN, null, null, undefined, undefined, {}, {}];
console.log(uniMap(arr)); // [ 1, 2, 3, '1', NaN, NaN, null, undefined, {}, {} ]

5. filter方法優化雙重循環中的外層循環

// filter方法
function uniMap(arr) {
    let res = [];
    return res = arr.filter( (item , index) => {
        return arr.indexOf(item) === index;
    })   //[ 1, 2, 3, '1', null, undefined ]
}
// main
let arr = [1, 2, 1, 3, '1', NaN, NaN, null, null, undefined, undefined, {}, {}];
console.log(uniMap(arr)); // [ 1, 2, 3, '1', null, undefined, {}, {} ]

此處的filter方法能夠和方法3,4排列組合用~~其實方法1也利用了filter方法(filter人氣高啊)filter方法原理已經說過,忘記的往上翻~對象

6. Object 方法

// object
function uniMap(arr) {
    let obj = {};
    return arr.filter( item => {
        return obj.hasOwnProperty(item) ? false : (obj[item] = true);
    })  
}
// main
let arr = [1, 2, 1, 3, '1', 2, 2, 2, NaN, NaN, null, null, undefined, undefined];
console.log(uniMap(arr)); // [ 1, 2, 3, NaN, null, undefined ]

上述代碼原理是利用一個空的 Object 對象,咱們把數組的值存成 Object 的 key 值,好比 Object[value1] = true,在判斷另外一個值的時候,若是 Object[value2]存在的話,就說明該值是重複的。從結果能夠看到,他把數字 1 和字符串 '1'當成了同一個字符,由於對象的key值均是字符串,數字1被轉換爲字符串了,所以該方法適用於你想把數字和字符串去重的場合。接口

特殊數據結構的去重判斷

去重的方法就到此結束了,然而根據上面的結果能夠看到,對於特殊的數據類型好比:null、undefined、NaN、對象等,不一樣的去重方法其實結果是不一樣的。那麼下面給個總結和分析。
對於例子中的這樣一個數組:
[1, 2, 1, 3, '1', 2, 2, 2, NaN, NaN, null, null, undefined, undefined];內存

方法 結果 說明
1.Map [ 1, 2, 3, '1', NaN, null, undefined, {}, {} ] 對象不去重
2.Set [ 1, 2, 3, '1', NaN, null, undefined, {}, {} ] 對象不去重
3.雙重循環 [ 1, 2, 3, '1', NaN, NaN, null, undefined, {}, {} ] 對象和NaN都不去重
4.內層index [ 1, 2, 3, '1', NaN, NaN, null, undefined, {}, {} ] 對象和NaN不去重
5.外層filter [ 1, 2, 3, '1', null, undefined, {}, {} ] 對象不去重NaN被忽略掉
6.Object方法 [ 1, 2, 3, NaN, null, undefined ] 數字和字符串去重,對象被忽略

之因此出現上面的結果,先看一下幾個判斷:

console.log(null === null);  // true
console.log(undefined === undefined); // true
console.log(NaN === NaN);  // false
console.log({} === {});  // false

再結合 indexOf 是使用 === 判斷,以及set map 也使用 === 判斷可是認爲 NaN 和 NaN 相等,即可以分析出來。

注意 對於數組元素和去重不是上述類型和結果的,那麼針對你想要的去重去靈活修改代碼,不能夠生搬硬套~~

相關文章
相關標籤/搜索