[TOC]
写作不易,Star是最大鼓励,感觉写的不错的可以给个Star⭐,请多多指教。Github地址。
原数组如下:
let originalArray = [1, '1', '1', 2, true, 'true', false, false, null, null, {}, {}, 'abc', 'abc', undefined, undefined, NaN, NaN];
 1. ES6的Set数据结构
ES6提供了新的数据结构Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。Set本身是一个构造函数,用来生成Set数据结构。
console.log([...new Set(originalArray)]); // [ 1, '1', 2, true, 'true', false, null, {}, {}, 'abc', undefined, NaN ]
// [ 1, '1', 2, true, 'true', false, null, {}, {}, 'abc', undefined, NaN ]
console.log(Array.from(new Set(originalArray)));
 2
3
Set并不是真正的数组,上面例子中的Array.from和扩展运算符...都可以将Set数据结构,转换成最终的结果数组。
这是最简单快捷的去重方法,但是细心的同学会发现,这里的 {} 没有去重。可是又转念一想,2个空对象的地址并不相同,所以这里并没有问题,结果ok。
Set缺点
Set无法对引用类型的元素去重。
const mySet = new Set();
mySet.add(1);
mySet.add(1);
mySet.add({name: 'lisi', age: 10});
mySet.add({name: 'lisi', age: 10});
console.log(mySet); // Set { 1, { name: 'lisi', age: 10 }, { name: 'lisi', age: 10 } }
 2
3
4
5
6
7
2. Map的has方法
把原数组的每一个元素作为key存到Map中。由于Map中不会出现相同的key值,所以最终得到的就是去重后的结果。
// Map的key
let originalArray = [1, '1', '1', 2, true, 'true', false, false, null, null, {}, {}, 'abc', 'abc', undefined, undefined, NaN, NaN];
const resultArr = [];
const map = new Map();
originalArray.forEach(item => {
    // 没有该key值
    if (!map.has(item)) {
        map.set(item, true);
        resultArr.push(item);
    }
});
console.log(resultArr); // [ 1, '1', 2, true, 'true', false, null, {}, {}, 'abc', undefined, NaN ]
 2
3
4
5
6
7
8
9
10
11
12
13
3. indexOf和includes
indexOf
let originalArray = [1, '1', '1', 2, true, 'true', false, false, null, null, {}, {}, 'abc', 'abc', undefined, undefined, NaN, NaN];
const resultArr = [];
originalArray.forEach(item => {
    // resultArr中没有该item的值
    if (resultArr.indexOf(item) < 0) {
        resultArr.push(item);
    }
});
console.log(resultArr); // [ 1, '1', 2, true, 'true', false, null, {}, {}, 'abc', undefined, NaN, NaN ]
 2
3
4
5
6
7
8
9
10
11
需要注意:indexOf并不没处理NaN。
includes
let originalArray = [1, '1', '1', 2, true, 'true', false, false, null, null, {}, {}, 'abc', 'abc', undefined, undefined, NaN, NaN];
const resultArr = [];
originalArray.forEach(item => {
    // resultArr中没有该item的值
    if (!resultArr.includes(item)) {
        resultArr.push(item);
    }
});
console.log(resultArr); // [1, '1', 2, true, 'true', false, null, {}, {}, 'abc', undefined, NaN]
 2
3
4
5
6
7
8
9
10
11
includes处理了NaN,结果ok。
4. sort
// sort
let originalArray = [1, '1', '1', 2, true, 'true', false, false, null, null, {}, {}, 'abc', 'abc', undefined, undefined, NaN, NaN];
// 对原数组进行排序
originalArray.sort();
// 将排序后的第一项放到结果数组中
const resultArr = [originalArray[0]];
for (let i = 1; i < originalArray.length; i++) {
    if (originalArray[i] !== resultArr[resultArr.length - 1]) {
        resultArr.push(originalArray[i]);
    }
}
console.log(resultArr); // [1, "1", 2, NaN, NaN, {}, {}, "abc", false, null, true, "true", undefined]
 2
3
4
5
6
7
8
9
10
11
12
13
需要注意,这种方法同样没有处理NaN。
5. 双层for循环 + splice
双层循环,外层循环遍历原数组,内层从i+1开始遍历比较,相同时删除这个值。
// 双层 for 循环 + splice
let originalArray = [1, '1', '1', 2, true, 'true', false, false, null, null, {}, {}, 'abc', 'abc', undefined, undefined, NaN, NaN];
for (let i = 0; i < originalArray.length - 1; i++) {
    for (let j = i + 1; j < originalArray.length - 1; j++) {
        if (originalArray[i] === originalArray[j]) {
            originalArray.splice(j, 1);
            // j--;
        }
    }
}
console.log(originalArray); // [1, "1", 2, true, "true", false, null, {…}, {…}, "abc", undefined, NaN, NaN]
 2
3
4
5
6
7
8
9
10
11
12
splice方法会修改原数组,所以这里我们并没有新开空数组去存储,最终输出的是修改之后的原数组。但同样的没有处理NaN。
6. 原始去重
// 原始去重
let originalArray = [1, '1', '1', 2, true, 'true', false, false, null, null, {}, {}, 'abc', 'abc', undefined, undefined, NaN, NaN];
let resArr = [originalArray[0]];
for (let i = 1; i < originalArray.length; i++) {
    let isRepeat = false;
    for (let j = 0; j < resArr.length; j++) {
        // 如果结果数组中已经存在该值,跳出内层循环
        if (originalArray[i] === resArr[j]) {
            isRepeat = true;
            break;
        }
    }
    if (!isRepeat) {
        resArr.push(originalArray[i]);
    }
}
console.log(resArr); // [1, "1", 2, true, "true", false, null, {}, {}, "abc", undefined, NaN, NaN]
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
这是最原始的去重方法,很好理解,但写法繁琐。同样的没有处理NaN。
7. ES5的reduce
// reduce
let originalArray = [1, '1', '1', 2, true, 'true', false, false, null, null, {}, {}, 'abc', 'abc', undefined, undefined, NaN, NaN];
let resArr = [];
resArr = originalArray.reduce((acc, cur) => {
    return acc.includes(cur) ? acc : [...acc, cur];
}, []);
console.log(resArr); // [ 1, '1', 2, true, 'true', false, null, {}, {}, 'abc', undefined, NaN ]
 2
3
4
5
6
7
8
9
8. 对象的属性(不推荐使用)
每次取出原数组的元素,然后在对象中访问这个属性,如果存在就说明重复。
let originalArray = [1, '1', '1', 2, true, 'true', false, false, null, null, {}, {}, 'abc', 'abc', undefined, undefined, NaN, NaN];
let resArr = [];
const obj = {};
for(let i = 0; i < originalArray.length; i++) {
    if(!obj[originalArray[i]]){
        resultArr.push(originalArray[i]);
        obj[originalArray[i]] = 1;
    }
}
obj = null; // 对象销毁,释放内存
console.log(resultArr);
// [1, 2, true, false, null, {…}, "abc", undefined, NaN]
 2
3
4
5
6
7
8
9
10
11
12
但这种方法有缺陷。从结果看,它貌似只关心值,不关注类型。还把 {} 给处理了,但这不是正统的处理办法,所以不推荐使用。
let originalArray = [1, '1', '1', 2, true, 'true', false, false, null, null, {}, {}, 'abc', 'abc', undefined, undefined, NaN, NaN];
let resArr = [];
let obj = {};
for (let i = 0; i < originalArray.length; i++) {
    let item = originalArray[i];
    if (obj[item]) {
        originalArray[i] = originalArray[originalArray.length - 1];
        originalArray.length--;
        i--;
        continue;
    }
    obj[item] = item;
}
obj = null;
console.log(originalArray); // [1, NaN, NaN, 2, true, undefined, false, false, null, null, {…}, undefined, "abc"]
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
9. filter + hasOwnProperty
filter 方法会返回一个新的数组,新数组中的元素,通过 hasOwnProperty 来检查是否为符合条件的元素。
const obj = {};
const resultArr = originalArray.filter(function (item) {
    return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true);
});
console.log(resultArr);
// [1, "1", 2, true, "true", false, null, {…}, "abc", undefined, NaN]
 2
3
4
5
6
7
这貌似是目前看来最完美的解决方案了。这里稍加解释一下:
- hasOwnProperty 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性。
 - typeof item + item 的写法,是为了保证值相同,但类型不同的元素被保留下来。例如:第一个元素为 number1,第二第三个元素都是 string1,所以第三个元素就被去除了。
 - obj[typeof item + item] = true 如果 hasOwnProperty 没有找到该属性,则往 obj 里塞键值对进去,以此作为下次循环的判断依据。
 - 如果 hasOwnProperty 没有检测到重复的属性,则告诉 filter 方法可以先积攒着,最后一起输出。
 
看似完美解决了我们源数组的去重问题,但在实际的开发中,一般不会给两个空对象给我们去重。所以稍加改变源数组,给两个空对象中加入键值对。
let originalArray = [1, '1', '1', 2, true, 'true', false, false, null, null, {a: 1}, {a: 2}, 'abc', 'abc', undefined, undefined, NaN, NaN];
 然后再用 filter + hasOwnProperty 去重。然而,结果竟然把 {a: 2} 给去除了!!!这就不对了。所以,这种方法有点去重 过头 了,也是存在问题的。 特别注意:字符串与对象拼接。
'1' + {name: 'lisi'} // "1[object Object]"
  10. lodash中的_.uniq
 console.log(_.uniq(originalArray));
// [1, "1", 2, true, "true", false, null, {…}, {…}, "abc", undefined, NaN]
 2
3
然后,我在好奇心促使下,看了它的源码,指向了baseUniq文件,它的源码如下:
function baseUniq(array, iteratee, comparator) {
  let index = -1
  let includes = arrayIncludes
  let isCommon = true
  const { length } = array
  const result = []
  let seen = result
  if (comparator) {
    isCommon = false
    includes = arrayIncludesWith
  }
  else if (length >= LARGE_ARRAY_SIZE) {
    const set = iteratee ? null : createSet(array)
    if (set) {
      return setToArray(set)
    }
    isCommon = false
    includes = cacheHas
    seen = new SetCache
  }
  else {
    seen = iteratee ? [] : result
  }
  outer:
  while (++index < length) {
    let value = array[index]
    const computed = iteratee ? iteratee(value) : value
    value = (comparator || value !== 0) ? value : 0
    if (isCommon && computed === computed) {
      let seenIndex = seen.length
      while (seenIndex--) {
        if (seen[seenIndex] === computed) {
          continue outer
        }
      }
      if (iteratee) {
        seen.push(computed)
      }
      result.push(value)
    }
    else if (!includes(seen, computed, comparator)) {
      if (seen !== result) {
        seen.push(computed)
      }
      result.push(value)
    }
  }
  return result
}
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
 _.uniqBy
 _.uniqBy方法可以通过指定 key,来专门去重对象列表。
_.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
// => [{ 'x': 1 }, { 'x': 2 }]
 2
 _.uniqWith
 _.uniqWith方法可以完全地给对象中所有的键值对,进行比较。
import _ from 'lodash';
<script>
var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 2 }];
_.uniqWith(objects, _.isEqual);
</script>
//=> [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]
 2
3
4
5
6
7
其中, _.isEqual(value,other)用于执行深比较来确定两者的值是否相等。_.uniqWith()做去重处理。