【查漏补缺】Map与Set数据结构

543 阅读8分钟

前言

本博客主要介绍set和map数据结构,但是在前面顺带一些解构赋值的语法示例,以缓解大家在看数据结构案例时对解构赋值的古怪语法产生不适应感,大神可以忽略。

解构赋值

由于解构赋值的用法形式多样且古怪,很多时候我们并不能知晓其具体作用,甚至花了三天学完语法,还是记不住,所以这里列举了一些常用场景,供大家参考

交换变量的值

传统方式

let x = 1
let y = 2
let z
z = x
x = y
y = z

ES6

let x =1
let y =2
[x,y]=[y,x]
x // 2
y // 1

使用这种方法,非常简洁,而且更加易读

函数返回多个值

function example(){
   return [1,2,3]
}
let [x,y,z]=example()

react官方教程中就是使用这种方式接受state数据的

function example(){
	return {x:1,y:2}
}
let {x,y}=example()

使用这种方法,就需要对应返回出来的对象的属性,不然就会失败

let [z,y]=example()
z // undefined
y //2

函数参数定义

function fn({x,y}){
     console.log(x,y)
}
fn({x:1,y:2}) // 1 2

上面的方式如果看不懂可以看成是以下方式

function fn(obj){
   let {x,y}=obj;
    console.log(x,y)
}
fn({x:1,y:2})

只不过是把obj这个变量直接删掉,把{x,y}搬到上面去了。

对数组来说也是一样的

function fn([x,y,z]){}
fn([1,2,3])

提取JSON数据

解构赋值对提取JSON数据来说,尤其有用

let jsonData = {
  id: 42,
  status: "OK",
  data: [867, 5309]
};

let { id, status, data: number } = jsonData;
console.log(id,status,number)
//或者
let {id, status, data}=jsonData;
console.log(id,status,data)

上面的方式结果都是一样的

map属性遍历

const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');

for (let [key, value] of map) {
  console.log(key + " is " + value);
}

如果只想要遍历value,不想要key,可以写成这样

for(let [,value] of map){}

模块中导出功能

const {useState,useEffect} from 'react'

iterable

xxxx

Set

SetES6新产生的数据解构,它类似于数组,不一样的是它没有重复的值。

生成Set数据解构

Set本身是构造函数,所以我们需要用new来生成一个Set数据解构

参数生成

const set=new Set([1,2,3,4,5])
set //Set(5) {1, 2, 3, 4, 5}

传递一个数组可以生成Set数据解构,实际上并非数组可以实现,具有iterable接口的数据都可以通过这种方式生成Set数据。

原型方法生成生成

使用Set.prototype.add方法也可以生成Set数据解构

const s= new Set()
for(let v of [1,2,3,1,2,3]){
	s.add(v)
}
// Set(3) {1, 2, 3}

上面可以看到,Set并不会录重复值

iterable接口数组生成示例

function fn(){
    return new Set(arguments)
}
fn(1,2,3,4,5,6)
// Set(6) {1, 2, 3, 4, 5, 6}

const set=new Set()
document.querySelectorAll('div').forEach(div=>{set.add(div)})

去重

const a=[1,2,1,2]
const b=[...new Set(a)]

上面代码使用...展开运算符来展开Set数据解构,再用[]包起来就可以变成数组

或者也可以使用Array.from()转数组,取决于你的爱好

const a=[1,2,1,2]
const b=Array.from(new Set(a))

字符串也可以去重

const a='123412'
const b=[...new Set(a)].join('')
b // '1,2,3,4'

上面的代码用于字符串去重,因为字符串也具有iterable接口,可以当参数传递给Set构造函数

Set实例的属性

Set.prototype.constructor:默认为构造函数Set

Set.prototype.size:返回Set实例的成员总数,跟数组的length差不多

Set实例的方法

操作方法

增:Set.prototype.add(value)

删:Set.prototype.delete(value)

查:Set.prototype.has(value)

清除:Set.prototype.clear()

遍历操作方法

Set.prototype.keys():返回键名的遍历器

Set.prototype.values():返回键值的遍历器

Set.prototype.entries():返回键值对的遍历器

Set.prototype.forEach():使用回调函数遍历每个成员

由于Set数据结构的键和值是一样的,所以keysvalues方法返回的结果是一致的,我们可以通过entries论证键和值是一致的这个观点

let set= new Set(['qiu','yan','xi'])
set.forEach((value,key)=>{
	console.log(`value:${value}=>key:${key}`)
})
//value:qiu=>key:qiu
//value:yan=>key:yan
//value:xi=>key:xi
for(let item of set.entries()){
	console.log(item)
}
//['qiu','qiu']
//['yan','yan']
//['xi','xi']

上面代码中,entries每次都会返回包含键值的数组。

遍历的应用

由于Set拥有iterable接口,也可以使用for of循环。

let set= new Set(['qiu','yan','xi'])
for(let x of set){
	console.log(x)
}
//qiu
//yan
//xi

配合...运算符就可以实现变相实现setmap或者filter等方法

let set=new Set([1,2,3,4,5,6])
new Set([...set].map((value,key)=>{return value*2}))
//Set(6) {2, 4, 6, 8, 10, 12}

new Set([...set].filter((value)=>{
return value>3
}))
//Set(3) {4, 5, 6}

Map

javascript中,object对象是以键值对的形式存在的,但是其键全部都是字符串,当我们希望使用hash表的时候,ES6提供的Map比对象更适合,它打破了键值对中键为字符串的限制,提供以值值对的数据结构。

生成Map数据结构

参数生成

Map也是一个构造函数,我们可以采用传入一个二元数组的方式来生成数据结构,数组内的数组会形成值值对。

let map=new Map([['name','qiuyanxi'],['age',10]])

//Map(2) {"name" => "qiuyanxi", "age" => 10}

原型方法生成

Map键的位置也可以是对象,我们采用set方法来生成Map数据结构(添加成员)

let map=new Map().set({name:'qiuyanxi'},'男')
//Map(1) {{name:'qiuyanxi'} => "男"}

实际上,在Map构造函数接收数组为参数时,实际上做的是以下操作

let array=[[name,'qiu'],[age,10]]
let map = new Map()
array.forEach(([key,value],index)=>{map.set(key,value)})
//Map(2) {"name" => "qiu", "age" => 10}

上面的代码中[key,value]实际上就是将[name,'qiu']进行解构赋值,然后对map对象进行set方法添加对应的成员。

事实上,只要持有iterable接口,且每个成员都是双元素数组结构都可以成为Map构造函数的参数。

Map原型方法

Map.prototype.size返回Map 结构的成员总数

Map.prototype.set(key, value)添加成员

Map.prototype.get(key)查成员

Map.prototype.delete(key)删除成员

Map.prototype.has(key)返回一个布尔值,表示某个键是否在当前 Map 对象之中。

Map.prototype.clear() 清除所有成员

const a = {'name':'qiu'}
const map = new Map()
map.set(a,'yanxi')
map.size //1
map.get(a) //'yanxi'
map.delete(a) //true
map.clear() 
// Map(0) {}

如果对同一个键多次赋值,则会覆盖原来的键

const map =new Map()
map.set(1,'123')
map.set(1,'456')
map.get(1) // '456'

需要注意的是,当键为对象类型时,我们读取它或者想覆盖它最好是使用引用的方式,而不是直接书写

const map=new Map()
map.set({name:"qiu"},'123')
map.set({name:"qiu"},'456')
map.get({name:"qiu"}) //undefined
map//Map(2) {{name:'qiu'} => "123", {name:'qiu'} => "456"}

上面的代码中,我使用map方法分别设置以{name:'qiu'}为对象的键名,但是实际上存在键名一致的情况,而并未出现值覆盖,并且get读取的时候读取出undefined,这是由于键的内存地址不同,Map不将其认同为一个键,所以我们要注意,在使用这种情况下,需要将键名转化为地址的引用

const map=new Map()
const n={name:"qiu"}
map.set(n,'123')
map.set(n,'456')
map.get(n) // '456'

由上可知,其实Map的键是和地址绑定的,只要内存地址不同,就被认为是两个不同的键。

如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,比如0和-0就是一个键,布尔值true和字符串true则是两个不同的键。另外,undefined和null也是两个不同的键。虽然NaN不严格相等于自身,但 Map 将其视为同一个键。

const map=new Map()
map.set(0,'123')
map.set(-0,'456')
map //Map(1) {0 => "456"}
map.set(true,123)
map.set('true',456)
map.set(null,666)
map.set(undefined,777)
map.set(NaN,888)
map.set(NaN,999)
map //Map(6) {0 => "456", true => 123, "true" => 456, null => 666, undefined => 777, NaN => 999}

遍历方法

Map结构原生有以下方法遍历 Map.prototype.keys()返回键名的遍历器

Map.prototype.values()返回值的遍历器

Map.prototype.entries()返回所有成员的遍历器

Map.prototype.forEach()遍历所有成员 Map的遍历顺序就是插入顺序

const map=new Map([[1,'true'],[2,'false']])
for(let k of map.keys()){console.log(k)} // 1 2

for(let k of map.values()){console.log(k)} // 'true' 'false'

for(let k of map.entries()){console.log(k)} //[1, "true"][2, "false"]

map.forEach((value,key)=>{
	console.log(value+':'+key)
}) //  true:1  false:2

Map转化

转化为数组

使用扩展运算符能快速将Map转化为数组,同时由于Map没有Map方法,使用扩展运算符配合则可以使用filter方法或者map方法

const map=new Map([[1, 'one'],
  [2, 'two'],
  [3, 'three']])
 const a=new Map([...map].filter(([key,value])=>{
  	return key>2
  }))
  a //Map(1) {3 => "three"}

转化为对象

如果所有 Map 的键都是字符串,它可以无损地转为对象,如果是非字符串,则会转为字符串后再转化成对象

const map=new Map([[8, 'one'],
  [9, 'two'],
  [10, 'three']])
  const obj={}
map.forEach((value,key)=>{
    obj[key]=value
})
obj //{8: "one", 9: "two", 10: "three"}

对象转成Map

使用Object.entries()方法可以将对象转化成成员包含键名,键值的二元数组,然后再转化成Map

//接上面代码
let o =Object.entries(obj) 
// [[8, 'one'],[9, 'two'],[10, 'three']]
let map =new Map(o)

Map转成JSON

我们需要根据不同情况转成不同的JSON对象

当键名都是字符串时,可以转成对象JSON

const map=new Map()
map.set('qiu','yanxi')
map.set('height','180')
function mapToString(map){
	const obj=Object.create(null)
    map.forEach((value,key)=>{
    obj[key]=value
    })
    return obj
}
const obj=mapToString(map)
JSON.stringify(obj) //"{"qiu":"yanxi","height":"180"}"

当键名不单单是字符串时,可以转成数组JSON

const map=new Map()
map.set([1,2,3],'yanxi')
map.set(['height'],'180')
JSON.stringify([...map])
//"[[[1,2,3],"yanxi"],[["height"],"180"]]"

参考文档

wangdoc.com/es6/set-map…