Mark24
JavaScript编程之数组去重
双层循环
最原始的双重循环:
var array = [1, 1, '1', '1'];
function unique(array) {
// res用来存储结果
var res = [];
for (var i = 0, arrayLen = array.length; i < arrayLen; i++) {
for (var j = 0, resLen = res.length; j < resLen; j++ ) {
if (array[i] === res[j]) {
break;
}
}
// 如果array[i]是唯一的,那么执行完循环,j等于resLen
if (j === resLen) {
res.push(array[i])
}
}
return res;
}
console.log(unique(array)); // [1, "1"]
在这个方法中,我们使用循环嵌套,最外层循环 array,里面循环 res,如果 array[i] 的值跟 res[j] 的值相等,就跳出循环,如果都不等于,说明元素是唯一的,这时候 j 的值就会等于 res 的长度,根据这个特点进行判断,将值添加进 res。
兼容性最好。
内层循环可以用 indefOf
替代
var array = [1, 1, '1'];
function unique(array) {
var res = [];
for (var i = 0, len = array.length; i < len; i++) {
var current = array[i];
if (res.indexOf(current) === -1) {
res.push(current)
}
}
return res;
}
console.log(unique(array));
排序后去重
数组sort排序之后,相同的值就会被排在一起,然后就可以只判断当前元素与上个元素是否相同,相同就重复,不同添加到新数组:
var array = [1, 1, '1'];
function unique(array) {
var res = [];
var sortedArray = array.concat().sort();
var seen;
for (var i = 0, len = sortedArray.length; i < len; i++) {
// 如果是第一个元素或者相邻的元素不相同
if (!i || seen !== sortedArray[i]) {
res.push(sortedArray[i])
}
seen = sortedArray[i];
}
return res;
}
console.log(unique(array));
根据已排序好的数组去重,效率是高于每次 indexOf
。
filter
ES5 提供了filter方法,我们可以用来简化外层循环,比如indexOf
:
var array = [1, 2, 1, 1, '1'];
function unique(array) {
var res = array.filter(function(item, index, array){
return array.indexOf(item) === index;
})
return res;
}
console.log(unique(array));
Object 键值对
利用一个空对象,把数组的值存成key,比如 Object[value1]=true 判断另一个值如果存在就说明是重复:
var array = [1, 2, 1, 1, '1'];
function unique(array) {
var obj = {};
return array.filter(function(item, index, array){
return obj.hasOwnProperty(item) ? false : (obj[item] = true)
})
}
console.log(unique(array)); // [1, 2]
这个方法存在一些问题,因为1和’1’本来是不同的,但是这种方法会判断是同一个值因为对象的key只能是字符串。可以用 typeof item+item 拼成字符串作为key来避免这个问题。
var array = [1, 2, 1, 1, '1'];
function unique(array) {
var obj = {};
return array.filter(function(item, index, array){
return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
})
}
console.log(unique(array)); // [1, 2, "1"]
即使是这样依然无法却分出两个对象,比如 {value:1}
,{value:2}
,因为例typeof item + item
结果将会都是 Object[object Object]
, 不过可以使用基于 JSON.stringify
将对象序列化:
var array = [{value: 1}, {value: 1}, {value: 2}];
function unique(array) {
var obj = {};
return array.filter(function(item, index, array){
console.log(typeof item + JSON.stringify(item))
return obj.hasOwnProperty(typeof item + JSON.stringify(item)) ? false : (obj[typeof item + JSON.stringify(item)] = true)
})
}
console.log(unique(array)); // [{value: 1}, {value: 2}]
ES6
ES6 提供了新的数据结构 Set他就是没有重复的值的集合,还有Map
Set
var array = [1, 2, 1, 1, '1'];
function unique(array) {
return Array.from(new Set(array));
}
console.log(unique(array)); // [1, 2, "1"]
简化版
function unique(array) {
return [...new Set(array)];
}
再简化
var unique = (a) => [...new Set(a)]
Map
Map是ES6中新增的数据结构,Map类似于对象,但是普通对象的值key必须是字符串或者数字,Map的key可以是任何类型。
function unique (arr) {
const seen = new Map()
return arr.filter((a) => !seen.has(a) && seen.set(a, 1))
}
特殊类型的比较
去重的元素可能是多种多样的,除了 1和’1’之外,还有 null, undefined, NaN, 对象
等,这些元素如何去重呢?
一些简单的例子:
var str1 = '1';
var str2 = new String('1');
console.log(str1 == str2); // true
console.log(str1 === str2); // false
console.log(null == null); // true
console.log(null === null); // true
console.log(undefined == undefined); // true
console.log(undefined === undefined); // true
console.log(NaN == NaN); // false
console.log(NaN === NaN); // false
console.log(/a/ == /a/); // false
console.log(/a/ === /a/); // false
console.log({} == {}); // false
console.log({} === {}); // false
对于这样的数组
var array = [1, 1, '1', '1', null, null, undefined, undefined, new String('1'), new String('1'), /a/, /a/, NaN, NaN];
上面各种方法的结果如何呢?
方法 | 结果 | 说明 |
---|---|---|
for循环 | [1, “1”, null, undefined, String, String, /a/, /a/, NaN, NaN] | 对象和 NaN 不去重 |
indexOf | [1, “1”, null, undefined, String, String, /a/, /a/, NaN, NaN] | 对象和 NaN 不去重 |
sort | [/a/, /a/, “1”, 1, String, 1, String, NaN, NaN, null, undefined] | 对象和 NaN 不去重 数字 1 也不去重 |
filter + indexOf | [1, “1”, null, undefined, String, String, /a/, /a/] | 对象不去重 NaN 会被忽略掉 |
filter + sort | [/a/, /a/, “1”, 1, String, 1, String, NaN, NaN, null, undefined] | 对象和 NaN 不去重 数字 1 不去重 |
优化后的键值对方法 | [1, “1”, null, undefined, String, /a/, NaN] | 全部去重 |
Set | [1, “1”, null, undefined, String, String, /a/, /a/, NaN] | 对象不去重 NaN 去重 |
Map | [1, “1”, null, undefined, String, String, /a/, /a/, NaN] | 对象不去重 NaN 去重 |
分析
一个demo可以看到原因
// demo1
var arr = [1, 2, NaN];
arr.indexOf(NaN); // -1
indexOf 底层是用 === 进行判断,而 NaN === NaN
结果是false,所以使用indexOf找不到 NaN。
// demo2
function unique(array) {
return Array.from(new Set(array));
}
console.log(unique([NaN, NaN])) // [NaN]
Set认为 NaN===NaN为宝false所以这两个元素是重复的。