JavaScript Set和Map数据结构

JavaScript Set和Map数据结构

文章目录

    • JavaScript Set和Map数据结构
      • 一.前言
      • 二.Set
        • 1.成员唯一
        • 2.属性方法
          • 1. size
          • 2.add()
          • 3.delete()
          • 4.has()
          • 5.clear()
        • 3.无法按下标查询,修改,排序
        • 4.Set的取值
          • 1.扩展运算符解构赋值
          • 2.forEach遍历
          • 3.for...of遍历
          • 4.keys()
          • 5.values()
          • 6.entries()
        • 5.Array和Set的对比
        • 6.Set的应用
          • 1.Set转为数组
          • 2.数组去重
          • 3.数组的map和filter可以间接用于Set
          • 4.实现并集 `(Union)`、交集 `(Intersect)` 和差集
      • 三.WeakSet
        • 1.WeakSet特点
        • 2.WeakSet成员弱引用
        • 3.不会引发内存泄漏
        • 4.使用WeakSet
        • 5.WeakSet的方法
      • 四.Map
        • 1.Map和原生Object的区别
        • 2.Map的使用
        • 3.属性方法
          • 1. size
          • 2.set(key,value)
          • 3.get(key)
          • 4.has(key)
          • 5.delete(key)
          • 6.clear()
        • 4.遍历Map
          • 1.forEach遍历
          • 2.for...of遍历
          • 3.keys()
          • 4.values()
          • 5.entries()
        • 5.数据类型转换
          • 1.Map转为数组
          • 2.数组转为Map
          • 3.Map转为对象
          • 4.对象转为Map
        • 6.重复键的情况
          • 1.引用类型情况
          • 2.值类型情况
      • 五.WeakMap
        • 1.特点
        • 2.设计目的
        • 3.WeakMap使用
        • 4应用场景
      • 六.总结
        • 1.Set
        • 2.WeakSet
        • 3.Map
        • 4.WeakMap

一.前言

ES6 提供了两种新的数据结构 Set和Map.Set是集合,Map是字典

二.Set

Set是一个构造函数,用来生成Set数据结构,它类似于数组,但是成员的值都是唯一的,没有重复的

初始化Set可以接受一个数组或类数组对象(或者具有iterable接口的其他数据结构)作为参数,也可以创建一个空的Set

let s1 = new Set();
let s2 = new Set([1, 2, 3, 4]);
let s3 = new Set(document.querySelectorAll('div'));
console.log(s1);//Set(0)
console.log(s2);//Set(4)

1.成员唯一

在Set中成员的值是唯一的,重复的值自动被过滤掉

let s3 = new Set([1, 2, 2, 3, 3, 4])
console.log(s3);//{1, 2, 3, 4}

2.属性方法

1. size

返回成员总数

let s3 = new Set([1, 2, 2, 3, 3, 4])
console.log(s3.size);//去重之后,成员总数为4
2.add()

添加某个值,返回Set结构本身(可以链式调用)

let s3 = new Set([1, 2, 2, 3, 3, 4])
console.log(s3.add([6,7]))//Set(5) {1, 2, 3, 4, Array(2)}
console.log(s3.add(5));//Set(5) {1, 2, 3, 4, 5}
3.delete()

删除某个值,返回一个bool值,表示是否删除成功

let s3 = new Set([1, 2, 2, 3, 3, 4])
console.log(s3.delete(4));//true
4.has()

返回一个bool值,表示该值是否为Set的成员

let s3 = new Set([1, 2, 2, 3, 3, 4])
console.log(s3.has(5));//false
5.clear()

清除所有成员,没有返回值

let s3 = new Set([1, 2, 2, 3, 3, 4])
s3.clear();
console.log(s3);//Set(0) {}

3.无法按下标查询,修改,排序

得益于数据结构Set查找更快速更高效,但是也因为数据结构的内部数据是无序的,无法实现按下标查询,修改,排序等操作

var array = [1, 2, 'a', 3, 'b'];
let s1 = new Set(array);
console.log(s1[0]);//undefined
console.log(s1['a']);//undefined

4.Set的取值

Set没有类似getter方法,取值怎么取呢?

1.扩展运算符解构赋值
var array = [1, 2, 'a', 3, 'b'];
let s1 = new Set(array);
var [a, b, c] = [...s1];
console.log(a);//1
console.log(b);//2
console.log(c);//a

var array=[...s1];
console.log(array);//[1, 2, "a", 3, "b"]
2.forEach遍历

使用回调函数遍历每个成员

var array = [1, 2, 'a', 3, 'b'];
let s1 = new Set(array);
s1.forEach((val, key, set) => {
    console.log("val", val);
    console.log("key", key);
    console.log("set", set);
});

Set结构的键名就是键值(两者同一个值),因此使用forEach遍历时,第一个参数和第二个参数的值永远都是一样

3.for…of遍历
var array = [1, 2, 'a', 3, 'b'];
let s1 = new Set(array);
for (const val of s1) {
    console.log(val);
}

可以循环遍历出Set中的每一个值

4.keys()

返回键名的遍历器

由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以 keys 方法和 values 方法的行为完全一致。

var array = [1, 2, 'a', 3, 'b'];
let s1 = new Set(array);
for (const val of s1.keys()) {
    console.log(val);
}
5.values()

返回键值的遍历器。

var array = [1, 2, 'a', 3, 'b'];
let s1 = new Set(array);
for (const val of s1.values()) {
    console.log(val);
}
//1
//2
//a
//3
//b
6.entries()

返回键值对的遍历器

var array = [1, 2, 'a', 3, 'b'];
let s1 = new Set(array);
for (const val of s1.values()) {
    console.log(val);
}
//[1, 1]
//[2, 2]
//["a", "a"]
//[3, 3]
//["b", "b"]

5.Array和Set的对比

  • ArrayindexOf方法比Sethas方法效率低下
  • Set不含有重复值(可以利用这个特性对数组进行去重)
  • Set通过delete方法删除某个值,而Array只能通过splice,两者的使用方便程度,前者更优.
  • Array的很多新方法map,filter,some,ervery等是Set没有的(但是两者可以互相转换来使用)

6.Set的应用

1.Set转为数组

使用Array.from方法可以将Set结构转为数组

const items = new Set([1, 2, 3, 4, 5])
const array = Array.from(items)
2.数组去重
// 方法一:
var arr1 = [1,2,4,2,3,5,1,3];
var arr2 = [...new Set(arr1)];
console.log(arr2);// [1, 2, 4, 3, 5]

// 方法二:
var arr1 = [1,2,4,2,3,5,1,3];
var arr2 = Array.from(new Set(arr1));
console.log(arr2);// [1, 2, 4, 3, 5]
3.数组的map和filter可以间接用于Set
let s2 = new Set([1, 2, 3, 4]);
s2 = new Set([...s2].map((ele) => {
    return ele * 2;
}));
console.log(s2);//Set(4) {2, 4, 6, 8}


let s2 = new Set([1, 2, 3, 4]);
s2 = new Set([...s2].filter((ele) => {
    return ele > 2;
}));
console.log(s2)//Set(2) {3, 4}
4.实现并集 (Union)、交集 (Intersect) 和差集

并集:2个集合合并,并且去掉重复的

交集:寻找2个集合或多个集合之间相同的成员

差集:所有属于A且不属于B的元素构成的集合,叫做集合A减集合B(或集合A与集合B之差)

(1)并集

let s1 = new Set([1, 2, 3]);
let s2 = new Set([3, 4, 5]);
let union = new Set([...s1, ...s2]);
console.log(union);

(2)交集

let s1 = new Set([1, 2, 3]);
let s2 = new Set([3, 4, 5]);
let intersect = new Set([...s1].filter((ele) => {
    return s2.has(ele);
}));
console.log(intersect);//Set(1) {3}

(3)差集

let s1 = new Set([1, 2, 3]);
let s2 = new Set([3, 4, 5]);
let difference = new Set([...s1].filter((ele) => {
    return !s2.has(ele);
}));
console.log(difference)//Set(2) {1, 2}

三.WeakSet

WeakSet结构与Set类似,也是不重复的值的集合

WeakSet的成员只能是对象(任何具有Iterable接口的对象),不能是其他类型的值(原生js对象不具备这个条件)

const a=[1,2];
new WeakSet(a);// Uncaught TypeError: Invalid value used in weak set

const ws = new WeakSet({ a: 1, b: 2 });
console.log(ws);//object is not iterable (cannot read property Symbol(Symbol.iterator))

上述代码会报错,因为WeakSet只能存储对象或者类似数组的对象

1.WeakSet特点

(1) 成员都是数组或类似数组的对象,若调用add()方法时,传入了非数组和类似数组的对象的参数,就会抛出错误

(2) 成员都是弱引用,可以被垃圾回收机制回收,可以用来保存DOM节点,不容易造成内存泄露.

(3) WeakSet不可迭代,因此不能被用在for...of等循环中.

(4) WeakSet没有size属性

2.WeakSet成员弱引用

WeakSet中的对象都是弱引用,也就是说:垃圾回收机制不考虑WeakSet对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存.

弱引用:弱引用就是不保证不被垃圾回收机制回收的对象,它拥有比较短暂的生命周期,在垃圾回收机制运行时,只要发现具有弱引用的对象,就会回收它的内存

3.不会引发内存泄漏

因为垃圾回收机制依赖引用计数,如果一个值的引用次数不为0,垃圾回收机制就不会释放这块内存.结束使用该值之后,有时忘记取消引用,导致内存无法释放,故而引起内存泄露,WeakSet里面的引用,都不计入垃圾回收机制,所以不存在这个问题,

因此,WeakSEt适合临时存放一组对象,以及存放跟对象绑定的信息,只要对象在外部消息,它在WeakSet里面的引用就会自动消失.

4.使用WeakSet

const ws = new WeakSet();

WeakSet 可以接受一个数组或类似数组的对象作为参数(实际上,任何具有Iterable接口的对象,都可以作为WeakSet的参数)

const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
console.log(ws);//WeakSet {Array(2), Array(2)}

注意:a数组的成员成为 WeakSet 的成员,而不是a数组本身。这意味着,数组的成员只能是对象。也就是说,如果我们要传数组的话必须使用[[1,2]]数组将里层对象或数组给包起来.

5.WeakSet的方法

  • WeakSet.prototype.add(value):向 WeakSet 实例添加一个新成员。
  • WeakSet.prototype.delete(value):清除 WeakSet 实例的指定成员。
  • WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在
const ws = new WeakSet();
const obj = {};
const foo = {};

ws.add(window);
ws.add(obj);

ws.has(window); // true
ws.has(foo);    // false

ws.delete(window);
ws.has(window);    // false

四.Map

Map是一个构造函数,用来生成Map数据结构,它类似于对象,也是键值对的集合,但是键可以是非字符串

Map中存储的是key-value形式的键值对,其中key和value可以是任何数据类型的,即对象也可以作为key,Map的出现,就是让各种类型的值都可以当做键,Map提供的是值-值的对应.也就是后端语言常用的数据字典

1.Map和原生Object的区别

(1) Object对象有原型,也就是说它有默认的key值在对象上面,除非我们使用object.create(null)创建一个没有原型的对象

(2) Object对象中,只能把string和symbol作为key值,但是在Map中,key值可以是任何数据类型的(String,Number,Boolean,Undefined,NaN,Null)或对象(Map,Set,Object,Function,Symbol)

(3) 通过Map中的size属性,可以很方便的获取Map的长度,要获取Object长度,只能手动计算

传统 Object 用字符串作键,Object 结构提供了“字符串 — 值”的对应Map 结构提供了“值 — 值”的对应,是一种更完善的 Hash 结构实现如果你需要“键值对”的数据结构,Map 比 Object 更快速 更高效 更合适。

2.Map的使用

初始化 Map 需要一个二维数组,或者直接初始化一个空的 Map

const m = new Map();
const m1 = new Map([["name", "liuqiao"], ["age", 27]]);
console.log(m);//Map(0) {}
console.log(m1);//Map(2) {"name" => "liuqiao", "age" => 27}

3.属性方法

1. size

返回成员总数

const m1 = new Map([["name", "liuqiao"], ["age", 27]]);
console.log(m1.size);//2
2.set(key,value)

设置键值对

const m1 = new Map([["name", "liuqiao"], ["age", 27]]);
m1.set("sex","男");
console.log(m1);//Map(3) {"name" => "liuqiao", "age" => 27, "sex" => "男"}
3.get(key)

获取键对应的值

const m1 = new Map([["name", "liuqiao"], ["age", 27]]);
console.log(m1.get("name"));//liuqiao
4.has(key)

是否存在某个键

const m1 = new Map([["name", "liuqiao"], ["age", 27]]);
console.log(m1.has("name"));//true
5.delete(key)

删除某个键值对,返回一个布尔值,表示是否删除成功

const m1 = new Map([["name", "liuqiao"], ["age", 27]]);
console.log(m1.delete("age"));//true
console.log(m1);// {"name" => "liuqiao"}
6.clear()

将Map中的所有元素全部删除

const m1 = new Map([["name", "liuqiao"], ["age", 27]]);
m1.clear();
console.log(m1);//Map(0) {}

4.遍历Map

1.forEach遍历
const m1 = new Map([["name", "liuqiao"], ["age", 27]]);
m1.forEach((val, key, map) => {
    console.log("val", val);
    console.log("key", key);
    console.log("map", map);
});
2.for…of遍历
const m1 = new Map([["name", "liuqiao"], ["age", 27]]);
for (const val of m1) {
    console.log(val);//打印的是数组
}

//可以使用如下方式,得到键和值
for (const [key,value] of m1) {
    console.log(key+" "+val);
}
3.keys()
const m1 = new Map([["name", "liuqiao"], ["age", 27]]);
for (let key of m1.keys()) {
    console.log(key)
}
//name,age
4.values()
const m1 = new Map([["name", "liuqiao"], ["age", 27]]);
for (let value of m1.values()) {
    console.log(value)
}
//liuqiao,27
5.entries()
const m1 = new Map([["name", "liuqiao"], ["age", 27]]);
for (const val of m1.entries()) {
    console.log(val);//打印的是数组
}

//可以使用如下方式,得到键和值
for (const [key,value] of m1.entries()) {
    console.log(key+" "+val);
}

实际上,你会发现,for...of...遍历map等同于使用map.entries()

5.数据类型转换

1.Map转为数组
const map=new Map();
const arr=[...map];
2.数组转为Map
let arr=[];
let map=new Map(arr);
3.Map转为对象
const obj={};
for (let [k, v] of map) {
  obj[k] = v
}
4.对象转为Map
let map=new Map();
for( let k of Object.keys(obj){
  map.set(k,obj[k])
}

6.重复键的情况

1.引用类型情况
const m1 = new Map();
m1.set(['a'], 5);
m1.set(['a'], 6);
console.log(m1);//Map(2) {Array(1) => 5, Array(1) => 6}
console.log(m1.get(['a']));//undefined

只有对同一个对象的引用,Map 结构才将其视为同一个键

Map的键本质上还是跟内存地址绑定的,只要内存地址不一样,就会看成2个不同的键

2.值类型情况
const m1 = new Map();
m1.set('name','zhangsan');
m1.set('name','lisi');
console.log(m1);//Map(1) {"name" => "lisi"}

值类型就不会有引用地址的问题,如果键名有重复的,则以后面的准,会把已经存在的替换掉

五.WeakMap

WeakMap结构与Map结构类似,也是用于生成键值对的集合

1.特点

  • 只接受对象作为键名(null 除外),不接受其他类型的值作为键名
  • 键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的
  • 不能遍历,方法有 getsethasdelete

2.设计目的

WeakMap设计目的在于,有时我们想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用

const e1 = document.getElementById('foo');
const e2 = document.getElementById('bar');
const arr = [
  [e1, 'foo 元素'],
  [e2, 'bar 元素'],
];

e1和e2是两个对象,我们通过arr数组对这两个对象添加文字说明,这就形成了arr对e1和e2的引用

WeakMap 就是为了解决这个问题而诞生的,它的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。

注意:WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。

const wm = new WeakMap();
let key = {};
let obj = {foo: 1};

wm.set(key, obj);
obj = null;
wm.get(key)
// Object {foo: 1}

3.WeakMap使用

const wm = new WeakMap();
const element = document.getElementById('example');
wm.set(element, 'some information');
wm.get(element) // "some information"

4应用场景

在网页的 DOM 元素上添加数据,就可以使用WeakMap结构。当该 DOM 元素被清除,其所对应的WeakMap记录就会自动被移除

<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>

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
}

六.总结

1.Set

  • 是一种叫做集合的数据结构(ES6新增的)
  • 成员唯一、无序且不重复
  • [value, value],键值与键名是一致的(或者说只有键值,没有键名)
  • 允许储存任何类型的唯一值,无论是原始值或者是对象引用
  • 可以遍历,方法有:adddeletehasclear

2.WeakSet

  • 成员都是对象
  • 成员都是弱引用,可以被垃圾回收机制回收,可以用来保存 DOM 节点,不容易造成内存泄漏
  • 不能遍历,方法有 adddeletehas

3.Map

  • 是一种类似于字典的数据结构,本质上是键值对的集合
  • 可以遍历,可以跟各种数据格式转换
  • 操作方法有:setgethasdeleteclear

4.WeakMap

  • 只接受对象作为键名(null 除外),不接受其他类型的值作为键名
  • 键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的
  • 不能遍历,方法有 getsethasdelete

你可能感兴趣的:(JavaScript)