JavaScript编程之数组去重

双层循环

最原始的双重循环:

var array = [1, 1, '1', '1'];

function unique(array) {
    // res用来存储结果
    var res = [];
    for (var i = 0, arrayLen = array.length; i < arrayLen; i++) {
        for (var j = 0, resLen = res.length; j < resLen; j++ ) {
            if (array[i] === res[j]) {
                break;
            }
        }
        // 如果array[i]是唯一的,那么执行完循环,j等于resLen
        if (j === resLen) {
            res.push(array[i])
        }
    }
    return res;
}

console.log(unique(array)); // [1, "1"]

在这个方法中,我们使用循环嵌套,最外层循环 array,里面循环 res,如果 array[i] 的值跟 res[j] 的值相等,就跳出循环,如果都不等于,说明元素是唯一的,这时候 j 的值就会等于 res 的长度,根据这个特点进行判断,将值添加进 res。

兼容性最好。

内层循环可以用 indefOf 替代

var array = [1, 1, '1'];

function unique(array) {
    var res = [];
    for (var i = 0, len = array.length; i < len; i++) {
        var current = array[i];
        if (res.indexOf(current) === -1) {
            res.push(current)
        }
    }
    return res;
}

console.log(unique(array));

排序后去重

数组sort排序之后,相同的值就会被排在一起,然后就可以只判断当前元素与上个元素是否相同,相同就重复,不同添加到新数组:


var array = [1, 1, '1'];

function unique(array) {
    var res = [];
    var sortedArray = array.concat().sort();
    var seen;
    for (var i = 0, len = sortedArray.length; i < len; i++) {
        // 如果是第一个元素或者相邻的元素不相同
        if (!i || seen !== sortedArray[i]) {
            res.push(sortedArray[i])
        }
        seen = sortedArray[i];
    }
    return res;
}

console.log(unique(array));

根据已排序好的数组去重,效率是高于每次 indexOf

filter

ES5 提供了filter方法,我们可以用来简化外层循环,比如indexOf:

var array = [1, 2, 1, 1, '1'];

function unique(array) {
    var res = array.filter(function(item, index, array){
        return array.indexOf(item) === index;
    })
    return res;
}

console.log(unique(array));

Object 键值对

利用一个空对象,把数组的值存成key,比如 Object[value1]=true 判断另一个值如果存在就说明是重复:

var array = [1, 2, 1, 1, '1'];

function unique(array) {
    var obj = {};
    return array.filter(function(item, index, array){
        return obj.hasOwnProperty(item) ? false : (obj[item] = true)
    })
}

console.log(unique(array)); // [1, 2]

这个方法存在一些问题,因为1和’1’本来是不同的,但是这种方法会判断是同一个值因为对象的key只能是字符串。可以用 typeof item+item 拼成字符串作为key来避免这个问题。

var array = [1, 2, 1, 1, '1'];

function unique(array) {
    var obj = {};
    return array.filter(function(item, index, array){
        return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
    })
}

console.log(unique(array)); // [1, 2, "1"]

即使是这样依然无法却分出两个对象,比如 {value:1},{value:2},因为例typeof item + item 结果将会都是 Object[object Object], 不过可以使用基于 JSON.stringify 将对象序列化:

var array = [{value: 1}, {value: 1}, {value: 2}];

function unique(array) {
    var obj = {};
    return array.filter(function(item, index, array){
        console.log(typeof item + JSON.stringify(item))
        return obj.hasOwnProperty(typeof item + JSON.stringify(item)) ? false : (obj[typeof item + JSON.stringify(item)] = true)
    })
}

console.log(unique(array)); // [{value: 1}, {value: 2}]

ES6

ES6 提供了新的数据结构 Set他就是没有重复的值的集合,还有Map

Set

var array = [1, 2, 1, 1, '1'];

function unique(array) {
   return Array.from(new Set(array));
}

console.log(unique(array)); // [1, 2, "1"]

简化版

function unique(array) {
    return [...new Set(array)];
}

再简化

var unique = (a) => [...new Set(a)]

Map

Map是ES6中新增的数据结构,Map类似于对象,但是普通对象的值key必须是字符串或者数字,Map的key可以是任何类型。

function unique (arr) {
    const seen = new Map()
    return arr.filter((a) => !seen.has(a) && seen.set(a, 1))
}

特殊类型的比较

去重的元素可能是多种多样的,除了 1和’1’之外,还有 null, undefined, NaN, 对象 等,这些元素如何去重呢?

一些简单的例子:

var str1 = '1';
var str2 = new String('1');

console.log(str1 == str2); // true
console.log(str1 === str2); // false

console.log(null == null); // true
console.log(null === null); // true

console.log(undefined == undefined); // true
console.log(undefined === undefined); // true

console.log(NaN == NaN); // false
console.log(NaN === NaN); // false

console.log(/a/ == /a/); // false
console.log(/a/ === /a/); // false

console.log({} == {}); // false
console.log({} === {}); // false

对于这样的数组

var array = [1, 1, '1', '1', null, null, undefined, undefined, new String('1'), new String('1'), /a/, /a/, NaN, NaN];

上面各种方法的结果如何呢?

方法 结果 说明
for循环 [1, “1”, null, undefined, String, String, /a/, /a/, NaN, NaN] 对象和 NaN 不去重
indexOf [1, “1”, null, undefined, String, String, /a/, /a/, NaN, NaN] 对象和 NaN 不去重
sort [/a/, /a/, “1”, 1, String, 1, String, NaN, NaN, null, undefined] 对象和 NaN 不去重 数字 1 也不去重
filter + indexOf [1, “1”, null, undefined, String, String, /a/, /a/] 对象不去重 NaN 会被忽略掉
filter + sort [/a/, /a/, “1”, 1, String, 1, String, NaN, NaN, null, undefined] 对象和 NaN 不去重 数字 1 不去重
优化后的键值对方法 [1, “1”, null, undefined, String, /a/, NaN] 全部去重
Set [1, “1”, null, undefined, String, String, /a/, /a/, NaN] 对象不去重 NaN 去重
Map [1, “1”, null, undefined, String, String, /a/, /a/, NaN] 对象不去重 NaN 去重

分析

一个demo可以看到原因

// demo1
var arr = [1, 2, NaN];
arr.indexOf(NaN); // -1

indexOf 底层是用 === 进行判断,而 NaN === NaN 结果是false,所以使用indexOf找不到 NaN。

// demo2
function unique(array) {
   return Array.from(new Set(array));
}
console.log(unique([NaN, NaN])) // [NaN]

Set认为 NaN===NaN为宝false所以这两个元素是重复的。

参考

Mark24

Everything can Mix.