JS数组去重

3,699 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情

数组去重

数组去重是一个很常见的需求,而实现的方式也多种多样,接下来我们来看看有哪些实现方式。

一、普通方法

创建一个空数组 arr,遍历原始数组(this)的每一项,如果当前项不存在 arr 中,则 pusharr

缺点:这里其实是两层遍历。第一层是外层的 for 循环,第二层是内部的 indexOf 判断

Array.prototype.unique = function() {
  var arr = [];
  var len = this.length;
  for(var i=0; i<len; i++) {
    if(arr.indexOf(this[i]) === -1) arr.push(this[i]);
  }
  return arr;
}

二、较快的方式

使用对象(hash)的方式来记录是否已经存在

Array.prototype.unique = function() {
  var json = {};
  var arr = [];
  var len = this.length;
  for(var i=0; i<len; i++) {
    if(typeof json[this[i]] === "undefined") {
      json[this[i]] = true;
      arr.push(this[i]);
    }
  }
  return arr;
}

但是有时我们会发现上述方法失灵了,例如数组:[1, "1", 2, 2]

我们希望去重之后得到的结果是:[1, "1", 2],但是会发现结果变成了 [1, 2]

这其实是因为我们在使用 hash 的时候,就是把数组元素作为 hashkey 值了,那么在使用过程中就会把数组元素变成字符串

  • 解决方案:使用 Object.prototype.toString.call

可以具体判断出 key 的数据类型

json[this[i]] = {}; // json[1] = {}
// json[1]["[object Number]"] = 1
// json[1]["[object String]"] = 1
json[this[i]][Object.prototype.toString.call(this[i])] = 1; 

json 中的每一项,也都是一个对象。内部对象的 key 值为当前循环项的类型,外部对象(json)的 key 值为当前循环项的字符串内容

例如:[1, "1"],在 json 的存储中就会变为

json = {
  1: {
    "[object Number]": 1, // value赋值为1,就可以统计重复的数量了,具有更好的扩展性
    "[object String]": 1
  }
}
Array.prototype.unique = function() {
  var json = {}, arr = [], len = this.length;
  for(let i=0; i<len; i++) {
    var type = Object.prototype.toString.call(this[i]); // 类型
    if(typeof json[this[i]] === "undefined") { // 如果json[1]为undefined,则对其初始化
      json[this[i]] = {}; // 初始化为空对象
      json[this[i]][type] = 1; // json[1]["[object Number]"] = 1,记录为1
      arr.push(this[i]); // 在新数组中添加当前项
    } else if(json[this[i]][type] === "undefined") { // json层的对象已经初始化过了,但内部type为key的内容还没记录过
      // 类似于:json[1]["[object Number]"]有值,但json[1]["[object String]"]未定义
      json[this[i]][type] = 1;
      arr.push(this[i]);
    } else { // 剩下的就是定义过的,直接+1就行了
      json[this[i]][type]++;
    }
  }
}

缺点:不能为对象去重。如果传入的数组为 [{ a: 1 }, {}],则去重的结果就是 [{ a: 1 }],因为对象转为字符串后都是 [object Object]

三、排序后对比

  • 先对数组进行排序:可以让相同的数据挨在一起
  • 定义一个新的空数组
  • 遍历排序后的数组
    • 如果当前项等于新数组的最后一项,则跳过
    • 如果不等于,则将当前项插入新数组
Array.prototype.unique = function() {
  this.sort(); // 先排序
  var arr = [], len = this.length;
  for(var i=0; i<len; i++) {
    if(this[i] !== arr[arr.length - 1]) { // 如果当前项不等于arr的最后一项
      arr.push(this[i])
    }
  }
  return arr
}

四、利用ES6的Set

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

不考虑兼容性的话,这种去重的方法代码量最少。

const arr = [1, "1", {}, {}];
console.log(unique(arr)); // [ 1, '1', {}, {} ]

通过上述代码的打印结果,可以看出 new Set 的方法可以区分出数据类型,但是无法去掉相同对象。(本质上是因为对象都是引用)

五、利用Map去重

function unique(arr) {
  const map = new Map();
  const array = [];
  for (let i = 0; i < arr.length; i++) {
    if (map.has(arr[i])) { // 如果已经存在了,则将其值设为 true(说明是重复元素)
      map.set(arr[i], true);
    } else { // 否则将其值设为false(目前没重复)
      map.set(arr[i], false);
      array.push(arr[i]);
    }
  }
  return array;
}