Set 和 Map,其应用场景,很多时候会用在数组去重和数据存储, Set 是一种叫做集合的数据结构,Map 是一种叫做字典的数据结构。
本文在gitthub做了收录:github.com/Michael-lzg…
数组去重12种方案(经典面试必问)
先总结一下数组的方法:
- pop
- push
- shift
- unshift
- slice
- splice
- sort
- reverse
- concat
- join
- indexOf
- lastIndexOf
- map
- forEach
还有其他可以处理数组的几个方法~
- includes:方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。
- find:返回第一次找到的那一项
- some:返回一个布尔值,只要一个是true,就返回true
- every:返回一个布尔值,需要每一项都是true,才返回true
- filter:返回一个过滤后的新数组;如果返回true就留下,false就过滤掉
- reduce:收敛
下面我们进入正题~(希望能对你有帮助~小编有点皮!!哈哈哈哈哈哈)
方法一:set :不是一种数据类型,是一种数据结构;成员唯一
let arr = [12,1,12,3,1,88,66,9,66];
function unique(ary) {
let s = new Set(ary);
// Array.from : 将set数据结构转成真正的数组;
return Array.from(s)
}
unique(arr);
复制代码
方法二:对象属性名不能重复
let arr = [12,1,12,3,1,88,66,9,66];
function unique(ary) {
let obj = {};
for(let i=0;i<ary.length;i++){
let cur = ary[i];
if(obj[cur]){
//ary.splice(i,1);// 导致数组塌陷
ary[i]=ary[ary.length-1];
ary.length--;// 删除最后一项
i--;
continue;
}
obj[cur]=cur;// 给obj新增键值对;属性名和属性值是一样的
}
}
unique(arr);
复制代码
方法三:indexOf
let arr = [12,1,12,3,1,88,66,9,66];
function unique(ary) {
let newAry = [];
for(let i=0;i<ary.length;i++){
let cur = ary[i];
if(newAry.indexOf(cur)===-1){
newAry.push(cur);
}
}
return newAry;
}
unique(arr)
复制代码
方法四:sort
let arr = [12,1,12,3,1,88,66,9,66];
function unique(ary) {
let a = ary.sort(function (a,b) {
return a-b;
});
for(let i=0;i<a.length;i++){
if(a[i]===a[i+1]){
a.splice(i+1,1);
i--;
}
}
return a;
}
unique(arr)
复制代码
方法五:includes :包含;如果数组包含那一项,返回true;不包含返回false;
let arr = [12,1,12,3,1,88,66,9,66];
function unique(ary) {
let newAry = [];
let len = ary.length;
for(let i=0;i<len;i++){
let cur = ary[i];
if(!newAry.includes(cur)){
newAry.push(cur);
}
}
return newAry;
}
console.log(unique(arr));
复制代码
方法六:hasOwnProperty : 检测属性名是否是对象的一个私有属性;返回一个布尔值;
let arr = [12,1,12,3,1,88,66,9,66];
function unique(ary) {
let obj = {};
return ary.filter(function (item,index,a) {
// item : 数组每一个成员
// index: 成员对应的索引
// a : 整个数组
// hasOwnProperty来校验的该属性是否出现过;
return obj.hasOwnProperty(typeof item+item)?false:obj[typeof item+item]=true;
if(obj.hasOwnProperty(typeof item+item)){
return false
}else{
obj[typeof item+item]=true;
return true;
}
})
}
console.log(unique(arr))
复制代码
方法七:filter+indexOf
let arr = [12,1,12,3,1,88,66,9,66];
function unique(ary) {
return ary.filter(function (item,index,a) {
return ary.indexOf(item)===index;
})
}
console.log(unique(arr));
复制代码
方法八:splice
let arr = [12,1,12,3,1,88,66,9,66];
function unique(ary) {
for(let i=0;i<ary.length;i++){
for(j=i+1;j<ary.length;j++){
if(ary[i]===ary[j]){
ary.splice(j,1);
j--;
}
}
}
return ary;
}
unique(arr);
复制代码
方法九:递归
let arr = [12,1,12,3,1,88,66,9,66];
function unique(ary) {
let len= ary.length;
ary = ary.sort(function (a,b) {
return a-b;
});
function loop(index) {
if(index>=1){
if(ary[index]===ary[index-1]){
ary.splice(index,1);
}
loop(index-1)
}
}
loop(len-1);
return ary;
}
console.log(unique(arr));
复制代码
方法十:Map :利用了Map数据结构存值的特点;
let arr = [12,1,12,3,1,88,66,9,66];
function unique(ary) {
let newAry =[];
let map = new Map();
for(let i=0;i<ary.length;i++){
if(!map.has(ary[i])){
map.set(ary[i],true);
newAry.push(ary[i]);
}
}
}
unique(arr);
复制代码
方法十一:reduce
let arr = [12,1,12,3,1,88,66,9,66];
function unique(ary) {
// reduce : 第一个是函数,第二个参数会传给第一次回调的prev;
return ary.reduce((prev,next)=>{
// 该函数返回值是下一次执行的prev;
return prev.includes(next)?prev:[...prev,next];
},[])
}
console.log(unique(arr));
复制代码
方法十二:类似于方法一的set,用了剩余运算符...
let arr = [12,1,12,3,1,88,66,9,66];
let a = [...new Set(arr)];
console.log(a);
\
Set
Set 本身是一个构造函数,用来生成 Set 数据结构。Set 函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。Set 对象允许你存储任何类型的值,无论是原始值或者是对象引用。它类似于数组,但是成员的值都是唯一的,没有重复的值。
const s = new Set()
[2, 3, 5, 4, 5, 2, 2].forEach((x) => s.add(x))
for (let i of s) {
console.log(i)
}
// 2 3 5 4
复制代码
Set 中的特殊值
Set 对象存储的值总是唯一的,所以需要判断两个值是否恒等。有几个特殊值需要特殊对待:
- +0 与 -0 在存储判断唯一性的时候是恒等的,所以不重复
undefined与undefined是恒等的,所以不重复NaN与NaN是不恒等的,但是在Set中认为NaN与NaN相等,所有只能存在一个,不重复。
Set 的属性:
size:返回集合所包含元素的数量
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5])
items.size // 5
复制代码
Set 实例对象的方法
add(value):添加某个值,返回Set结构本身(可以链式调用)。delete(value):删除某个值,删除成功返回true,否则返回false。has(value):返回一个布尔值,表示该值是否为Set的成员。clear():清除所有成员,没有返回值。
s.add(1).add(2).add(2)
// 注意2被加入了两次
s.size // 2
s.has(1) // true
s.has(2) // true
s.has(3) // false
s.delete(2)
s.has(2) // false
复制代码
遍历方法
keys():返回键名的遍历器。values():返回键值的遍历器。entries():返回键值对的遍历器。forEach():使用回调函数遍历每个成员。
由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以 keys 方法和 values 方法的行为完全一致。
let set = new Set(['red', 'green', 'blue'])
for (let item of set.keys()) {
console.log(item)
}
// red
// green
// blue
for (let item of set.values()) {
console.log(item)
}
// red
// green
// blue
for (let item of set.entries()) {
console.log(item)
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
复制代码
Array 和 Set 对比
Array的indexOf方法比Set的has方法效率低下Set不含有重复值(可以利用这个特性实现对一个数组的去重)Set通过delete方法删除某个值,而Array只能通过splice。两者的使用方便程度前者更优Array的很多新方法map、filter、some、every等是Set没有的(但是通过两者可以互相转换来使用)
Set 的应用
1、Array.from 方法可以将 Set 结构转为数组。
const items = new Set([1, 2, 3, 4, 5])
const array = Array.from(items)
复制代码
2、数组去重
// 去除数组的重复成员
;[...new Set(array)]
Array.from(new Set(array))
复制代码
3、数组的 map 和 filter 方法也可以间接用于 Set
let set = new Set([1, 2, 3])
set = new Set([...set].map((x) => x * 2))
// 返回Set结构:{2, 4, 6}
let set = new Set([1, 2, 3, 4, 5])
set = new Set([...set].filter((x) => x % 2 == 0))
// 返回Set结构:{2, 4}
复制代码
4、实现并集 (Union)、交集 (Intersect) 和差集
let a = new Set([1, 2, 3])
let b = new Set([4, 3, 2])
// 并集
let union = new Set([...a, ...b])
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter((x) => b.has(x)))
// set {2, 3}
// 差集
let difference = new Set([...a].filter((x) => !b.has(x)))
// Set {1}
复制代码
weakSet
WeakSet 结构与 Set 类似,也是不重复的值的集合。
- 成员都是数组和类似数组的对象,若调用
add()方法时传入了非数组和类似数组的对象的参数,就会抛出错误。
const b = [1, 2, [1, 2]]
new WeakSet(b) // Uncaught TypeError: Invalid value used in weak set
复制代码
- 成员都是弱引用,可以被垃圾回收机制回收,可以用来保存 DOM 节点,不容易造成内存泄漏。
WeakSet不可迭代,因此不能被用在for-of等循环中。WeakSet没有size属性。
Map
Map 中存储的是 key-value 形式的键值对, 其中的 key 和 value 可以是任何类型的, 即对象也可以作为 key。 Map 的出现,就是让各种类型的值都可以当作键。Map 提供的是 “值-值”的对应。
Map 和 Object 的区别
Object对象有原型, 也就是说他有默认的key值在对象上面, 除非我们使用Object.create(null)创建一个没有原型的对象;- 在
Object对象中, 只能把String和Symbol作为key值, 但是在Map中,key值可以是任何基本类型(String,Number,Boolean,undefined,NaN….),或者对象(Map,Set,Object,Function,Symbol,null….); - 通过
Map中的size属性, 可以很方便地获取到Map长度, 要获取Object的长度, 你只能手动计算
Map 的属性
- size: 返回集合所包含元素的数量
const map = new Map()
map.set('foo', ture)
map.set('bar', false)
map.size // 2
复制代码
Map 对象的方法
set(key, val): 向Map中添加新元素get(key): 通过键值查找特定的数值并返回has(key): 判断Map对象中是否有Key所对应的值,有返回true,否则返回falsedelete(key): 通过键值从Map中移除对应的数据clear(): 将这个Map中的所有元素删除
const m = new Map()
const o = { p: 'Hello World' }
m.set(o, 'content')
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
复制代码
遍历方法
keys():返回键名的遍历器values():返回键值的遍历器entries():返回键值对的遍历器forEach():使用回调函数遍历每个成员
const map = new Map([
['a', 1],
['b', 2],
])
for (let key of map.keys()) {
console.log(key)
}
// "a"
// "b"
for (let value of map.values()) {
console.log(value)
}
// 1
// 2
for (let item of map.entries()) {
console.log(item)
}
// ["a", 1]
// ["b", 2]
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value)
}
// "a" 1
// "b" 2
// for...of...遍历map等同于使用map.entries()
for (let [key, value] of map) {
console.log(key, value)
}
// "a" 1
// "b" 2
复制代码
数据类型转化
Map 转为数组
let map = new Map()
let arr = [...map]
复制代码
数组转为 Map
Map: map = new Map(arr)
复制代码
Map 转为对象
let obj = {}
for (let [k, v] of map) {
obj[k] = v
}
复制代码
对象转为 Map
for( let k of Object.keys(obj)){
map.set(k,obj[k])
}
复制代码
Map的应用
在一些 Admin 项目中我们通常都对个人信息进行展示,比如将如下信息展示到页面上。传统方法如下。
<div class="info-item">
<span>姓名</span>
<span>{{info.name}}</span>
</div>
<div class="info-item">
<span>年龄</span>
<span>{{info.age}}</span>
</div>
<div class="info-item">
<span>性别</span>
<span>{{info.sex}}</span>
</div>
<div class="info-item">
<span>手机号</span>
<span>{{info.phone}}</span>
</div>
<div class="info-item">
<span>家庭住址</span>
<span>{{info.address}}</span>
</div>
<div class="info-item">
<span>家庭住址</span>
<span>{{info.duty}}</span>
</div>
复制代码
js 代码
mounted() {
this.info = {
name: 'jack',
sex: '男',
age: '28',
phone: '13888888888',
address: '广东省广州市',
duty: '总经理'
}
}
复制代码
我们通过 Map 来改造,将我们需要显示的 label 和 value 存到我们的 Map 后渲染到页面,这样减少了大量的html代码
<template>
<div id="app">
<div class="info-item" v-for="[label, value] in infoMap" :key="value">
<span>{{label}}</span>
<span>{{value}}</span>
</div>
</div>
</template>
复制代码
js 代码
data: () => ({
info: {},
infoMap: {}
}),
mounted () {
this.info = {
name: 'jack',
sex: '男',
age: '28',
phone: '13888888888',
address: '广东省广州市',
duty: '总经理'
}
const mapKeys = ['姓名', '性别', '年龄', '电话', '家庭地址', '身份']
const result = new Map()
let i = 0
for (const key in this.info) {
result.set(mapKeys[i], this.info[key])
i++
}
this.infoMap = result
}
复制代码
WeakMap
WeakMap 结构与 Map 结构类似,也是用于生成键值对的集合。
- 只接受对象作为键名(
null除外),不接受其他类型的值作为键名 - 键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的
- 不能遍历,方法有
get、set、has、delete
总结
Set
- 是一种叫做集合的数据结构(ES6新增的)
- 成员唯一、无序且不重复
[value, value],键值与键名是一致的(或者说只有键值,没有键名)- 允许储存任何类型的唯一值,无论是原始值或者是对象引用
- 可以遍历,方法有:
add、delete、has、clear
WeakSet
- 成员都是对象
- 成员都是弱引用,可以被垃圾回收机制回收,可以用来保存
DOM节点,不容易造成内存泄漏 - 不能遍历,方法有
add、delete、has
Map
- 是一种类似于字典的数据结构,本质上是键值对的集合
- 可以遍历,可以跟各种数据格式转换
- 操作方法有:
set、get、has、delete、clear
WeakMap
- 只接受对象作为键名(
null除外),不接受其他类型的值作为键名 - 键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的
- 不能遍历,方法有
get、set、has、delete
双重for循环去重
优点:
兼容性好
'1'和1可以区分
缺点:
对象不可以去重 因为对象不可以用来比较
NaN不可以去重
Array.prototype.unique = function () {
var newArr = [],
isRepeat,
len = this.length;
for (var i = 0; i < len; i++) {
isRepeat = false;
for (var j = 0; j < newArr.length; j++) {
if (this[i] === newArr[j]) {
isRepeat = true;
break;
}
}
if (!isRepeat) {
newArr.push(this[i]);
}
}
return newArr;
}复制代码
indexOf去重
优点:
'1'和1可以区分
缺点:
对象不可以去重 因为对象不可以用来比较
NaN不可以去重
Array.prototype.unique = function() {
var res = [],
len = this.length;
for (var i = 0; i < len; i++) {
var current = this[i];
if (res.indexOf(current) === -1) {
res.push(current);
}
}
return res;
}复制代码
相邻元素去重
优点:
'1'和1可以区分
缺点:
对象不可以去重 因为对象不可以用来比较
NaN不可以去重
不能识别undefined
Array.prototype.unique = function () {
var newArr = [],
len = this.length;
this.sort(function(a, b) { // 改变原数组
return a-b;
});
for (var i = 0; i < len; i++) {
if (this[i] !== this[i + 1]) {
newArr.push(this[i]);
}
}
return newArr;
}复制代码
filter
优点:
'1'和1可以区分
缺点:
对象不可以去重 因为对象不可以用来比较
NaN识别不了
Array.prototype.unique = function() {
var res = this.filter(function(item, index, array){
return array.indexOf(item) === index;
})
return res;
}复制代码
includes
优点:
'1'和1可以区分
NaN可以去重
缺点:
对象不可以去重 因为对象不可以用来比较 \
Array.prototype.unique = function () {
var newArr = [];
this.forEach(item => {
if (!newArr.includes(item)) {
newArr.push(item);
}
});
return newArr;
}复制代码
reduce
优点:
可以区分'1'和1
缺点:
对象不可以区分
NaN不可以区分
Array.prototype.unique = function () {
return this.sort().reduce((init, current) => {
if (init.length === 0 || init[init.length - 1] !== current) {
init.push(current);
}
return init;
}, []);
}复制代码
对象键值对
基本思路:利用了对象的key不可以重复的特性来进行去重。
优点:
NaN可以去重
正则可以去重
缺点:
'1'和1不能区分
对象不可以去重 因为对象作为 key 会变成 [object Object]
Array.prototype.unique = function () {
var obj = {},
arr = [],
len = this.length;
for (var i = 0; i < len; i++) {
if (!obj[this[i]]) {
obj[this[i]] = 'abc'; // 不能是 = this[i] 万一数组去重0
arr.push(this[i]);
}
}
return arr;
}
// 改变版本1
Array.prototype.unique = function () {
const newArray = [];
const tmp = {};
for (let i = 0; i < this.length; i++) {
if (!tmp[typeof this[i] + this[i]]) {
tmp[typeof this[i] + this[i]] = 1;
newArray.push(this[i]);
}
}
return newArray;
}
// 改进版本2
Array.prototype.unique = function () {
const newArray = [];
const tmp = {};
for (let i = 0; i < this.length; i++) {
// 使用JSON.stringify()进行序列化
if (!tmp[typeof this[i] + JSON.stringify(this[i])]) {
// 将对象序列化之后作为key来使用
tmp[typeof this[i] + JSON.stringify(this[i])] = 1;
newArray.push(this[i]);
}
}
return newArray;
}
复制代码
Map
原理: key对应value,key和value唯一 ,任何值都可以当属性
优点:
NaN可以去重
缺点:
对象不可以去重
Array.prototype.unique = function () {
var newArr = [],
tmp = new Map(),
len = this.length;
for (var i = 0; i < len; i++) {
if (!tmp.get(this[i])) {
tmp.set(this[i], 1);
newArr.push(this[i]);
}
}
return newArr;
}
//简化版
Array.prototype.unique = function() {
var map = new Map()
return arr.filter((a) => !map.has(a) && map.set(a, 1))
}复制代码
Set
原理: 它类似于数组,但是成员的值都是唯一的,没有重复的值
优点:
NaN去重
缺点:
对象不可以去重
Array.prototype.unique = function () {
return [...new Set(this)];
}
原文章连接:juejin.cn/post/684490…
作者原文链接:juejin.cn/post/684490…
作者原文链接:juejin.cn/post/684490…