双层循环

  1. const array = [1, 2, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 6];
  2. function unique(array) {
  3. var res = [];
  4. for (var i = 0, arrayLen = array.length; i < arrayLen; i++) {
  5. for (var j = 0, resLen = res.length; j < resLen; j++) {
  6. if (array[i] === res[j]) {
  7. break;
  8. }
  9. }
  10. // 如何元素是唯一的话,内层循环的 j 变量等于 res 的长度。
  11. if (j === resLen) {
  12. res.push(array[i]);
  13. }
  14. }
  15. return res;
  16. }
  17. console.log(unique(array)); // [ 1, 2, 3, 4, 5, 6 ]

res 数组与 array 进行循环比较,如何元素没有重复的话,内层循环的变量 j 会等于 res 的长度。

那有没有办法简化循环了?有的可以利用 indexOf 函数。

  1. const array = [1, 2, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 6];
  2. function unique1(array) {
  3. var res = [];
  4. for (var i = 0, arrayLen = array.length; i < arrayLen; i++) {
  5. var current = array[i];
  6. // 如何元素是唯一的话,返回 -1。
  7. if (res.indexOf(current) === -1) {
  8. res.push(current);
  9. }
  10. }
  11. return res;
  12. }
  13. console.log(unique(array)); // [ 1, 2, 3, 4, 5, 6 ]

排序后去重

如何是已经排序好的数组对它去重很容易联想到对比前一个元素和后后一个元素,如果相同的话就是重复,不相同的话就是不重复。

在效率方面排序后去重肯定比双层循环效率高。

  1. const array = [1, 2, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 6];
  2. function unique2(array) {
  3. var res = [];
  4. // concat 函数放回原数组的浅拷贝,不改变原数组。
  5. var sortedArray = array.concat().sort();
  6. var seen;
  7. for (var i = 0, len = array.length; i < len; i++) {
  8. if (!i || seen !== sortedArray[i]) {
  9. res.push(sortedArray[i]);
  10. }
  11. seen = sortedArray[i];
  12. }
  13. return res;
  14. }
  15. console.log(unique(array)); // [ 1, 2, 3, 4, 5, 6 ]

整合成一个去重函数

根据上面的两种方法,我们可以写一个复合函数。这个函数有两个参数:array(去重数组)、isSorted(是否已排序)。

  1. var array = [1, 2, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 6];
  2. function unique(array, isSorted) {
  3. var res = [],
  4. seen;
  5. for (var i = 0, arrayLen = array.length; i < arrayLen; i++) {
  6. if (isSorted) {
  7. if (!i || seen !== array[i]) {
  8. res.push(array[i]);
  9. }
  10. seen = array[i];
  11. } else {
  12. if (res.indexOf(array[i]) === -1) {
  13. res.push(array[i]);
  14. }
  15. }
  16. }
  17. return res;
  18. }
  19. console.log(unique(array, true)); // [ 1, 2, 3, 4, 5, 6 ]

添加一个新需求

规定 ‘a’ 和 ‘A’ 是同一个元素,但上面的方法是过滤不了这种情况的。怎么办了?能不能在执行判断之前将元素转换成小写了,照着这个思路很容易想到,对每个元素判断之前执行一个自定义的函数,在现在这种情况这个自定义函数就是可以将元素转换为小写。

改进上面的代码

  1. var array = [1, 1, 'a', 'A', 2, 2];
  2. function unique(array, isSorted, iteratee) {
  3. var res = [];
  4. var seen = [];
  5. for (var i = 0, len = array.length; i < len; i++) {
  6. var value = array[i];
  7. var computed = iteratee ? iteratee(value, i, array) : value;
  8. if (isSorted) {
  9. if (!i || seen !== computed) {
  10. res.push(value);
  11. }
  12. seen = computed;
  13. } else if (iteratee) {
  14. // 为什么这里不用 array 来判断,其实这样做是没有意义的,因为 array 都是没有转换过的元素。
  15. if (seen.indexOf(computed) === -1) {
  16. seen.push(computed);
  17. res.push(value);
  18. }
  19. } else if (res.indexOf(value) === -1) {
  20. res.push(value);
  21. }
  22. }
  23. return res;
  24. }
  25. console.log(
  26. unique(array, false, function(item) {
  27. return typeof item == 'string' ? item.toLowerCase() : item;
  28. })
  29. ); // [ 1, 'a', 2 ]

filter 方法

可以用 filter 方法,简化外层循环。

indexOf

  1. var array = [1, 1, 1, '1', 'a', 'A', 'a'];
  2. function unique(array, iteratee) {
  3. const seen = [];
  4. var res = array.filter(function(item, index, array) {
  5. var computed = iteratee ? iteratee(item, index, array) : item;
  6. seen.push(computed);
  7. return seen.indexOf(item) === index;
  8. });
  9. return res;
  10. }
  11. console.log(
  12. unique(array, function(item, index, array) {
  13. return typeof item === 'string' ? item.toLowerCase() : item;
  14. })
  15. ); // [1, '1', 'a']

排序

  1. var array = [1, 1, 1, '1', 'a', 'A', 'a'];
  2. function unique(array, iteratee) {
  3. return array
  4. .concat()
  5. .sort()
  6. .filter(function(item, index, array) {
  7. const after = iteratee ? iteratee(item, index, array) : item;
  8. const before = iteratee ? iteratee(array[index - 1], index, array) : array[index - 1];
  9. return !index || after !== before;
  10. });
  11. }
  12. console.log(
  13. unique(array, function(item, index, array) {
  14. return typeof item === 'string' ? item.toLowerCase() : item;
  15. })
  16. ); // [ 1, '1', 'A' ]

object 键值对

利用 hasOwnProperty 方法。

  1. var array = [1, 2, 1, 1, '1'];
  2. function unique(array) {
  3. var obj = {};
  4. return array.filter(function(item, index, array){
  5. // !! 将赋值表达式的副作用转为 boolean
  6. return obj.hasOwnProperty(item) ? false : !!(obj[item] = true)
  7. })
  8. }
  9. console.log(unique(array)); // [1, 2]

这种方法分辨不 1 和 ‘1’ 的区别,这是因为 object 的键名都是字符串。

改进一下:用 typeof item + item 作为 key。

  1. var array = [1, 2, 1, 1, '1'];
  2. function unique(array) {
  3. var obj = {};
  4. return array.filter(function(item, index, array){
  5. return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
  6. })
  7. }
  8. console.log(unique(array)); // [1, 2, "1"]

但是改进后还是分辨了不了对象元素,再改进:用 JSON.stringify 序列化一下。

  1. var array = [{value: 1}, {value: 1}, {value: 2}];
  2. function unique(array) {
  3. var obj = {};
  4. return array.filter(function(item, index, array){
  5. console.log(typeof item + JSON.stringify(item))
  6. return obj.hasOwnProperty(typeof item + JSON.stringify(item)) ? false : (obj[typeof item + JSON.stringify(item)] = true)
  7. })
  8. }
  9. console.log(unique(array)); // [{value: 1}, {value: 2}]

ES6 的 set 和 map

set

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

map

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

参考:

[1] JavaScript专题之数组去重