数组去重是一个特别常见的问题,大多时候也能想起一两种常用的方式来解决,但是具体有多少种方式呢,好想还没试过。
以下面这个数组为例,写一个unique的函数来进行去重处理。
var arr = [1,1,'1', '1', '', '', true,'true',true,false,false,'false',undefined,'undefined',undefined,null,'null',null, {}, {}, [], [], NaN, NaN];
unique(arr);
for + indexOf 循环:
不能对对象和NaN去重
function unique(arr) {
var tempArr = [];
var len = arr.length;
for(var i = 0; i<len; i++) {
if(tempArr.indexOf(arr[i]) < 0) {
tempArr.push(arr[i]);
}
}
return tempArr;
}
// [1, "1", "", true, "true", false, "false", undefined, "undefined", null, "null", {…}, {…}, Array(0), Array(0), NaN, NaN]
for + 排序后去重 (改变了输出顺序)
不能对对象和NaN去重
注意,JS中的sort函数排序是按照字符编码的顺序进行排序,排序后,只需要循环判断数组中的每一个元素在这个数组中的indexOf的位置是否是自己的下标(有重复值时返回的第一个下标一定不是当前元素的下标)
1、使用indexOf比较下标
function unique(arr) {
arr = arr.sort();
var tempArr = [];
var len = arr.length;
for(var i = 0; i<len; i++) {
if(arr.indexOf(arr[i]) === i) {
tempArr.push(arr[i]);
}
}
return tempArr;
}
// 这个方法的输出顺序会改变,且NaN会被忽略
// ["", Array(0), Array(0), 1, "1", {…}, {…}, false, "false", null, "null", true, "true", "undefined", undefined]
2、相邻数据是否相等判断法 另外一种是判断相邻两个元素是否相等,因为排序后的数组,如果有重复的值那就肯定会有相邻元素相等的情况。
function unique(arr) {
arr = arr.sort();
var tempArr = [];
var len = arr.length;
for(var i = 1; i<len; i++) {
if(arr[i] !== arr[i-1]) {
tempArr.push(arr[i-1]);
}
}
tempArr.push(arr[len - 1]);
return tempArr;
}
-
for循环同样可以通过for of 和foreach来改写,都是循环遍历
-
排序去重的方式中,通过对比前一个后一个值的方法会出现问题,例如当'undefined', undefined, 'undefined'几个被sort排序后,不会区分字符串类型,这时候前后对比会判断不出来,同理,null和'null', null这样的顺序出现时,会出现没有正确去重的情况。不过可以用于普通数组的去重处理。如数组等于
[1,'1',1, '1', '', '', true,'true',true,false,'false',false, null,'null',null]时,此时输出其实为:
["", 1, "1", 1, "1", false, "false", false, null, "null", null, true, "true", true]
filter+排序后去重 (改变了输出顺序)
ES5 提供的 filter 方法可以代替for循环,下面改写前面的两种去重方式:
filter + indexOf:
function unique (arr) {
return arr.sort().filter(function(item, index) {
return arr.indexOf(item) === index;
});
}
// ["", Array(0), Array(0), 1, "1", {…}, {…}, false, "false", null, "null", true, "true", "undefined", undefined]
filter + 相邻数据是否相等:
function unique (arr) {
return arr.sort().filter(function(item, index) {
return item !== arr[index-1];
});
}
// ["", Array(0), Array(0), 1, "1", NaN, NaN, {…}, {…}, false, "false", null, "null", null, true, "true", true, "undefined", undefined]
该方法同上一项 for+相邻数据是否相等 的方法会产生相同问题
利用对象存储
PS: 下面这个方法一有问题,当数组中存在字符串区分不了,比如1和'1',这个方法会判定为重复,所以只适用于纯number类型或同一类型的数组
方法一:
// 1、for循环
function unique(arr) {
var obj = {};
var len = arr.length;
var tempArr = [];
for(var i = 0; i < len; i++) {
if(!obj.hasOwnProperty(arr[i])) {
tempArr.push(arr[i]);
obj[arr[i]] = true;
}
}
return tempArr;
}
// 2、filter
function unique (arr) {
var obj = {};
return arr.filter(function(item) {
return obj.hasOwnProperty(item) ? false : (obj[item] = true);
})
}
// 输出:[1, "", true, false, undefined, null, {…}, NaN]
// 该方法将1和'1'当作重复元素进行处理了
解决办法:
使用typeof item + item拼接字符串作为对象的key值,例如1存储为number1,'1'存储为string1:
方法二:
// 1、for循环
function unique(arr) {
var obj = {};
var len = arr.length;
var tempArr = [];
for(var i = 0; i < len; i++) {
if(!obj.hasOwnProperty(typeof arr[i]+arr[i])) {
tempArr.push(arr[i]);
obj[typeof arr[i]+arr[i]] = true;
}
}
return tempArr;
}
// 2、filter
function unique (arr) {
var obj = {};
return arr.filter(function(item) {
return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true);
})
}
// [1, "1", "", true, "true", false, "false", undefined, "undefined", null, "null", {…}, Array(0), NaN]
注意: 优化后的这种方式既能对对象去重也能对NaN去重
循环 + includes
function unique(array) {
var temp = [];
array.forEach(element => {
if(!temp.includes(element)) {temp.push(element)};
});
return temp;
}
// [1, "1", "", true, "true", false, "false", undefined, "undefined", null, "null", {…}, {…}, Array(0), Array(0), NaN]
注意: filter的这种方式不能对对象去重,但能对NaN去重
ES6的解决办法:Set、Map
ES6中提供了新的数据结构 Set。类似于数组,但是成员的值都是唯一的,没有重复的值。
function unique(array) {
return Array.from(new Set(array));
}
接着使用解构赋值改写一下:
function unique(array) {
return [...new Set(array)];
}
Map结构:
Es6提供了Map数据结构,它类似于对象,也是键值对的集合,但是‘键’的范围不限于字符串,各种类型的值(包括对象)都可以作为键名。
function unique(array) {
const arr = new Map();
return array.filter(function(item) {
return !arr.has(item) && arr.set(item, 1);
})
}
// [1, "1", "", true, "true", false, "false", undefined, "undefined", null, "null", {…}, {…}, Array(0), Array(0), NaN]
注意: Set和Map的方式对对象不去重,NaN去重
最后总结一下,一共六种去重方式:
- 1、for+indexOf
- 2、for+排序后(indexOf比较下标法或相邻数据相等法)去重
- 3、前两种for改成filter
- 4、对象存储法
- 5、for(或者filter或者foreach) + includes , 本质同方法一
- 6、ES6的Set结构
不过以上排序方法多少会存在一些问题, 如:
-
只有方法4——对象存储法能对对象和NaN去重;
-
方法6——Map和Set能对NaN去重但不能对对象去重;
-
使用indexOf+排序后的方法会忽略NaN;
-
排序方法中,相邻数据相等法对undefined、null、true等遇到字符串类型描述时去重会有问题;