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 相等,即可以分析出来。

注意 对于数组元素和去重不是上述类型和结果的,那么针对你想要的去重去灵活修改代码,不能够生搬硬套~~

相关文章
相关标签/搜索