面试常考的点之一吧(我也只被考过set的用法而已)
先上最简单版~①利用set
[... new Set(arr)]
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。new Set(array)得到的是一个Set数组(没有重复值),三个点为扩展运算符,得到用逗号分隔的参数序列,最外的中括号使得我们最终得到的还是一个数组。这句话也可以写成Array.from(new Set(arr))。不能写成Array(new Set(arr)),否则会转化成[Set(size)],因为new Set(arr)得到的是一个Set,如果你还想得到数组必须Array(... new Set(arr)),但这其实已经和[... new Set(arr)]没有区别了。在此我们复习下Array.of(3)和Array(3)的区别。
Array.of(3); // [3]
Array(3); // [ , , ]
如果参数是两个及两个以上,他们表示的东西没有区别。
我们再来复习下扩展运算符,除了运用于数组去重,扩展运算符还可以展开数组,所以不再需要apply方法将数组转为函数的参数。我们来看两个例子
// ES5 的写法
Math.max.apply(null, [14, 3, 77])
// ES6 的写法
Math.max(...[14, 3, 77])
// 等同于
Math.max(14, 3, 77);
// ES5 的写法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);
// ES6 的写法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
arr1.push(...arr2);
注意push()函数后面可以跟多个参数!
提到Set不由得让人想起他的好兄弟Map,在有Map之前,对象Object的键(属性)只能是字符串,如果不是字符串就会调用toString()方法将其转换成字符串。为了解决这个问题,Map应运而生,甚至连数组、函数、对象都可以当Map的键。
var map = new Map();
var k1 = ['a'];
var k2 = ['a'];
map.set(k1, 111);
map.set(k2, 222);
map.get( k1 ); //111
map.get( k2 ); //222
以上这个例子也说明了尽管k1、k2值相等,但他们指向的位置不同,所以在Map中也是两个不同的键。
提到Map不由得让人想到Object,所以接下来引出我们的第二种方法:②采用对象keys方法,对象中的 key 永远不会重复
Array.prototype.unique = function(){
var obj = {};
//这个地方不能用for in因为for/in循环会遍历所有可枚举的属性
//包括自有和继承两种
//而且for/in中i代表的是index
//可以用for/of
//此时的i代表数组第i个值
for(var i of this){
obj[i] = i;
}
// this.forEach(function(element) {
// obj[element] = element;
// }, this);
return Object.keys(obj);
}
Object.keys(obj)返回对象的所有属性组成的数组,不包括原型方法和属性,也不能遍历到不可枚举的属性。此处插入一句:for in用来遍历对象,可以遍历到原型上的方法和属性,但是不能遍历到不可枚举的属性,当然如果不想要可以用hasOwnProperty判断一下是不是自有属性。
for (var key in object) {
if(object.hasOwnProperty(key)){
console.log(key);
}
}
for of更适合用于数组,因为他不会遍历到原型上的属性和自己本身的索引,只会遍历数组内的元素。
这个地方用到了forEach(),在这里我又踩到了一个坑
array.forEach(function(currentValue, index, arr), thisValue)
请看参数部分,第一个参数是个函数,函数的三个参数分别是当前值、索引和数组;第二个参数是函数所使用的this值,请看一下代码,区分两种的不同。
var obj = {
name: 'xx',
times: [1, 2, 3],
print: function() {//匿名函数的调用者是global/window
var that = this//指向obj;
this/*指向obj*/.times.forEach(function(n) {
console.log(this/*原本指向window,被that绑定到obj,*/.name)
}/*前面声明了,此处作为参数传入forEach函数*/)
}
};
obj.print();//此时返回三个空,因为this指的是window
var obj = {
name: 'xx',
times: [1, 2, 3],
print: function() {
var that = this//指向obj;
this/*指向obj*/.times.forEach(function(n) {
console.log(this/*原本指向window,被that绑定到obj,*/.name)
},that/*前面声明了,此处作为参数传入forEach函数*/)
}
};
obj.print();//三个xx
匿名函数的this指向的是全局,于是出现了你不指定this就会得到空的结果(因为全局里找不到name属性)。
③采用forEach方法
Array.prototype.unique = function(){
var n = [];
this.forEach(function(element) {
// if(n.indexOf(element) === -1){
// n.push(element);
// }
//includes用来判断一个数组是否包含一个指定的值
if(!n.includes(element)){
n.push(element);
}
});
return n;
}
此处用到了includes()函数,用来判断一个数组里是否拥有某个元素。
④采用filter方法
Array.prototype.unique = function() {
//filter调用时使用参数 (element, index, array)
//所以v=element,i=index
return this.filter((v, i) => this.indexOf(v) === i);
//返回一个新的通过测试的元素的集合的数组
}
filter()函数需要注意的是他也和forEach一样不会改变原数组(map,some,every亦是如此)。
⑤采用indexOf方法
Array.prototype.unique = function(){
var n = [];//存放已遍历的满足条件的元素
for(var i=0; i<this.length; i++){
if(n.indexOf(this[i]) === -1){
n.push(this[i]);
}
}
return n;
}
⑥采用slice方法
Array.prototype.unique = function(){
for(var i=this.length; i>=0; i--){
if(this.indexOf(this[i]) !== i){
this.splice(i,1);
}
}
return this;
}
最后这种方法记住是倒着走,就可以省去网上很多还要减1的麻烦啦