JS专题之数组去重

前言

数组去重在平常开发中的使用频率仍是较高的,也是网上随便一抓一大把的话题,因此,我写这篇文章目的在于概括和总结,既然不少人都在提的数组去重,本身到底了解多少呢。又或者是若是本身在开发中遇到了去重的需求,本身能想到更好的解决方案吗。javascript

此次咱们来理一理怎么作数组去重才能作得最合适,既要考虑兼容性,也要考虑性能和代码的优雅。java

个人学习路径是模仿冴羽(github: mqyqingfeng)的学习方式,感谢像冴羽这样优秀的人在前面领跑,我不想光看不作,因此多实践多输出,但愿将来能走出我本身的路。git

1、入门方案

function unique(origin) {
    var result = [];
    for(var i = 0; i < origin.length; i++) {
        var arrayItem = origin[i];

        for(var j= 0; j< result.length; j++) {
            var resultItem = result[j];
            
            // 若是在结果数组循环中找到了该元素,则跳出循环,进入下一个源数组元素的判断
            if(resultItem === arrayItem) {
                break;
            }
        }
        
        // 若是把结果数组循环完都没有找到该元素,就将该元素压入结果数组中
        if(j === result.length) {
            result.push(arrayItem);
        }
    }
    return result;
}

var array = ['a', 'b', 'c', '1', 0, 'c', 1, '', 1, 0];
console.log(unique(array));  // ["a", "b", "c", "1", 0, 1, ""]

以上代码是最简单实现数组去重的方式,优势在于兼容性极好,缺点就是两次 for 循环,时间复杂度为 O(n^2),性能较差。github

2、数组的 indexOf 属性

数组中的 indexOf 属性是 ES5 的规范,只有 IE8 及更早版本不支持该方法。相对来讲,若是你不须要兼容 IE8 的话,尽可能用 indexOf 来判断一个元素是否在数组中。算法

function unique(origin){
    var result = [];
    for(var i = 0; i< origin.length; i++) {
        var item = origin[i];
        if(result.indexOf(item) === -1) {
            result.push(item);
        }
    }
    return result;
}

3、数组的 filter 属性

数组的 filter() 方法建立一个新的数组,新数组中的元素是经过检查指定数组中符合条件的全部元素。

filter 的回调有三个参数,其中第三个参数是当前元素属于的数组对象,这样咱们能够继续利用 indexOf 属性啦。数组

function unique(origin) {
    var result = origin.filter(function (item, index, array){
        // 获取元素在源数组的位置,只返回那些索引等于当前元素索引的值。
        return array.indexOf(item) === index;
    });
    return result;
}

filter 兼容到 IE9, 这种方法没有 for 循环,主要利用了 filter 和 indexOf 属性,因此代码相对比较优雅。数据结构

4、利用 Object 的 key value

function unique(origin) {
    var result = [];
    var hashTable = {};
    for(var i = 0; i< origin.length; i++) {
        // 若是键对应的值,为真,意味着对象的键中已经有重复的键了。
        if(!hashTable[origin[i]]) {
        // 将元素做为对象的键,默认键对应的值为 true, 
            hashTable[origin[i]] = true;
            
            // 若是对象中没有这个键,则将这个元素放入结果数组中去。
            result.push(origin[i]);
        }
    }
    return result;
}

这种方案的事件复杂度为 O(n), 可是对象的键,默认是字符串类型,这意味着什么呢,数字 1 和 字符串 '1',在键中是相等的,因此,上面这种方法不适合字符串和数字混合的去重。闭包

因此咱们将元素的类型也放入对象的键中:app

function unique(origin) {
    var result = [];
    var hashTable = {};
    for(var i = 0; i< origin.length; i++) {
        var current = origin[i];
        // 字符串拼接元素的类型和元素
        var key = typeof(current) + current;
        if(!hashTable[key]) {
            hashTable[key] = true;
            result.push(current);
        }
    }
    return result;
}

5、数组的 sort 方法

function unique(origin) {
    return origin.concat.sort().filter(function(item, index, array) {
        // !index 表示第 0 个元素应该被返回。
        return !index || item !== origin[index-1]
    })
}

function unique(array) {
    array.sort(); // 排序字符串
    array.sort(function(a, b) {
        return a-b; // 排序数字
    })
    
    for(let i=0; i<array.length; i++) {
        if(array[i] === array[i+1]) {
            array.splice(i, 1);
            i--; // 应该将前一个数删除,而不是删除后一个数。是由于元素被删除以后,后面元素的索引会迁移,因此要 i--;
        }
    }
    return array;
}

sort 方法的优势在于利用了排序,返回后一个和前一个不相等的元素。比较简洁和直观。缺点在于改变了元素的原本的排序位置。函数

6、ES6 Set

ES6 提供了新的数据结构 Set,它相似于数组,可是成员的值都是惟一的,没有重复的值。向 Set 加入值的时候,不会发生类型转变,因此 5 和 '5' 是两个不一样的值。Set内部判断两个值是否相同,用的是相似于 "==="的算法,可是区别是,在set内部认为NaN 等于 NaN ;

Set 能够转换为数组,因此很容易实现去重

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

7、ES6 Map

ES6 新增了 Map 数据结果,经过 has 和 set 方法就能很方便的对前面的 object key value 方案进行优化。

function unique(origin){
    const map = new Map()
    return origin.filter((item) => !map.has(item) && map.set(item, true))
}

8、类型判断

一些常见的数据类型是 ===indexOf 是没法检测的,举个例子:

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

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

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

console.log(1 === new String('1'))  // false;

var arr = [NaN];
console.log(arr.indexOf(NaN)); // -1

因此在判断的时候,若是数据里有 NaN 和对象时要避免使用 indexOf===;

前面 Set 那里说过了,因此 Set 方法是能够去重 NaN的。

总结

数据去重在网上已经看烦了,但仍是想专门写一篇文章来实践和总结,能在工做中多几个思路也是极好的。感谢那些热爱分享和喜欢输出的人。

欢迎关注个人我的公众号“谢南波”,专一分享原创文章。

掘金专栏 JavaScript 系列文章

  1. JavaScript之变量及做用域
  2. JavaScript之声明提高
  3. JavaScript之执行上下文
  4. JavaScript之变量对象
  5. JavaScript之原型与原型链
  6. JavaScript之做用域链
  7. JavaScript之闭包
  8. JavaScript之this
  9. JavaScript之arguments
  10. JavaScript之按值传递
  11. JavaScript之例题中完全理解this
  12. JavaScript专题之模拟实现call和apply
  13. JavaScript专题之模拟实现bind
  14. JavaScript专题之模拟实现new
  15. JS专题之事件模型
  16. JS专题之事件循环
  17. JS专题之去抖函数
  18. JS专题之节流函数
  19. JS专题之函数柯里化
  20. JS专题之数组去重
相关文章
相关标签/搜索