题目描述
编写一个Javascript函数,传入一个数组,对数组中的元素进行去重并返回一个无重复元素的数组,数组的元素可以是数字、字符串、数组和对象。举例说明:
1. 如传入的数组元素为[123,"meili","123","mogu",123],则输出:[123,"mogu"]
2. 如传入的数组元素为[123,[1,2,3],"2","meili"],"meili"]
3. 如传入的数组元素为[123,{a: 1},{a: {b: 1}},{a: "1"},"meili"]
第一种解法:map+Set
把数组中的对象解析成字符串的方法用的是JSON.stringify()方法
var arr1 = [123,"meili"];
var arr2 = [123,123];
var arr3 = [123,"meili"];
function unique(arr) {
var b = arr.map(item=>{
return JSON.stringify(item);//JSON.stringify(item)是把数组中的对象解析成字符串再比较
})
var c = Array.from(new Set(b));//Set(b)用来对b去重,但是去重后的结果不是我们想要的数组的形式,所以需要用 Array.from()的方法变成我们想要的数组的形式
var d = c.map(item=>{
return JSON.parse(item);
})
return d;
console.log(unique(arr1));
console.log(unique(arr2));
console.log(unique(arr3));

1.1 filter
使用filter代替map,结果出来的结果不是想要的

注意:filter返回的是filter返回的是在一定条件下返回值为true的原数组项,所以此处最适合的是map或者for循环
所以如果需要从原数组中返回满足一定条件的数组,可以使用filter。
1.2 map和filter
红宝书中是这样描述的:
- filter()----创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
- map()----返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。按照原始数组元素顺序依次处理元素。
1.3 map和foreach
相同点
- 都是会遍历数组的每一个元素,
- 同样是三个参数
- 都是只能遍历数组
不同点
- forEach()方法不会返回执行结果,而是undefined,红宝书中说的是没有返回值。
- 而map()方法会得到一个新的数组并返回
使用场景
- forEach适合于你并不打算改变数据的时候,而只是想用数据做一些事情 – 比如存入数据库或则打印出来
- map()适用于你要改变数据值的时候。不仅仅在于它更快,而且返回一个新的数组
forEach到底会不会改变原数组
1.4 Array.from
Array.from
方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)
1.4.1 将两类对象转化为真正的数组
-
类数组对象所谓类似数组的对象,本质特征只有一点,即必须有length
属性
let arrayLike = {
'0': 'a','1': 'b','2': 'c',length: 3
};
// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a','b','c']
// ES6的写法
let arr2 = Array.from(arrayLike); // ['a','c']
关于call的思考
-
只要是部署了 Iterator 接口的数据结构,Array.from
都能将其转为数组。
Array.from('hello')
// ['h','e','l','o']
let namesSet = new Set(['a','b'])
Array.from(namesSet) // ['a','b']
补充:扩展运算符(...
)也可以将某些数据结构转为数组。扩展运算符背后调用的是遍历器接口(Symbol.iterator
),因此只要具有 Iterator 接口的对象,都可以使用扩展运算符。
let nodeList = document.querySelectorAll('div');
let array = [...nodeList];
上面代码中,querySelectorAll
方法返回的是一个NodeList
对象。它不是数组,而是一个类似数组的对象。这时,扩展运算符可以将其转为真正的数组,原因就在于NodeList
对象实现了 Iterator
- 比如 Map 结构是具有 Iterator 接口的对象,可以使用扩展运算符
let map = new Map([
[1,'one'],[2,'two'],[3,'three'],]);
let arr = [...map.keys()]; // [1,3]
- Generator 函数运行后,返回一个遍历器对象,因此也可以使用扩展运算符。
const go = function*(){
yield 1;
yield 2;
yield 3;
};
[...go()] // [1,3]
上面代码中,变量go
是一个 Generator 函数,执行后返回的是一个遍历器对象,对这个遍历器对象执行扩展运算符,就会将内部遍历得到的值,转为一个数组。
- 如果对没有 Iterator 接口的对象,使用扩展运算符,将会报错。
const obj = {a: 1,b: 2};
let arr = [...obj]; // TypeError: Cannot spread non-iterable object
-
本身是数组的话
Array.from([1,3])
// [1,3]
1.4.2 Array.from还可以接受第二个参数
作用类似于数组的
map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
Array.from(arrayLike,x => x * x);
// 等同于
Array.from(arrayLike).map(x => x * x);
Array.from([1,(x) => x * x)
// [1,4,9]
1.4.3 如果map
函数里面用到了this
关键字,还可以传入Array.from
的第三个参数,用来绑定this
。
1.4.4 将字符串转为数组,然后返回字符串的长度
function countSymbols(string) {
return Array.from(string).length;
}
第二种解法 :双重遍历
思想:通过拿出一个元素和剩下的元素依次比较,如果全部不相等则证明此元素为唯一
var arr1 = [123,"meili"];
var arr3 = [123,123];
var arr2 = [123,"meili"];
function unique(arr) {
let b=[]
for(let i=0;i<arr.length;i++){
let unexit=true
for(let j=i+1;j<arr.length;j++){
if(JSON.stringify(arr[i])===JSON.stringify(arr[j])){
unexit=false
break
}
else{
unexit=true
}
}
if(unexit){
b.push(arr[i])
}
}
return b
}
console.log(unique(arr1));
console.log(unique(arr2));
console.log(unique(arr3));

缺点:顺序会改变
第三种解法:Object.keys():存在唯一性
var arr1 = [123,"meili"];
function unique(arr) {
let b=[]
let hash={}
for(let i=0;i<arr.length;i++){
if(!hash[JSON.stringify(arr[i])]){
hash[JSON.stringify(arr[i])]=true
b.push(arr[i])
}
}
return b
}
console.log(unique(arr1));
console.log(unique(arr2));
console.log(unique(arr3));
这三种方法都可以实现对象去重,第二种存在一些缺点,顺序会改变。
参考:https://www.aspzz.cn/article/134411.htm
http://es6.ruanyifeng.com/#docs/set-map#Map
https://juejin.im/post/5ca7514a6fb9a05e790a46a4