JS中的(Weak)Set和(Weak)Map你都懂吗?

lgmyxbjfu
发布于 2020-9-6 11:04
浏览
0收藏

写此篇文章的原因:

经常碰到Set、WeakSet、Map、WeakMap,但是不确定使用场景,想知道使用场景和它们之间的区别
为什么有Set和Map,还需要再有WeakSet和WeakMap?
看了鲁米(伊斯兰教苏菲派诗人)的诗【任何你每天持之以恒在做的事情,都可以为你打开一扇通向精神深处,通向自由之门】,让我更加激发要坚持的决心。
在进入正文前,首先了解一个名词迭代器(iterable),参考我的![]()
Set


定义
Set是集合数据结构

特点
Set成员是唯一且无序的,没有重复值。
Set允许存储任何类型的值,无论是原始值还是对象引用
向Set中加入值的时候,不会进行类型转换,类似于精确运算符“===”,主要的区别在于NaN等于自身,而精确运算符“===”判断NaN不等于自身。
Set函数可以接受一个数组(或者具有iterable接口的其他数组结构)作为参数,用来初始化
const set = new Set(document.querySelectorAll('div))

let set1 = new Set({a:1})
console.log(set1)
> Uncaught TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator))
    at new Set (<anonymous>)

let set1 = new Set([{a:1}]) 
console.log(set1)  //Set(1) {{…}}

方法和属性
实例属性constructor:构造函数
size: 返回元素数量
let set = new Set([1,2,3,2])
set.length //undefined
set.size   //3   自动去重


实例方法

add(value):新增
delete(value):存在即删除集合中的value
clear():清空集合
Array.from和...可以将Set结构转化为数组
let set = new Set([1,2,3,3,2])
let array = Array.from(set)
console.log(array)  //[1, 2, 3]

console.log([...set]) //[1, 2, 3]


遍历方法:

keys():返回一个包含集合中所有键的迭代器
values():返回一个包含集合中所有值的迭代器
entries():返回一个包含set对象中所有元素得键值对迭代器
forEach(callback,thisArg):用于对集合成员执行callback

let set = new Set([1,2,3,3,2])
for(let item of set.keys()){
  console.log(item) //1 2 3
}
for(let item of set.values()){
  console.log(item) //1 2 3
}
for(let item of set.entries()){
  console.log(item) // [1, 1]  [2, 2] [3, 3]
}
set.forEach((value,key)=>{
  console.log(key + ':' + value)  //1:1  2:2  3:3
})



由此想到跟对象结构使用遍历函数的区别

let obj = {1:'a',2:'b',3:'c'}
for(let item of Object.keys()){
  console.log(item) //1 2 3
}
for(let item of Object.values()){
  console.log(item) //a b c
}
for(let item of Object.entries()){
  console.log(item) // [1, 'a']  [2, 'b'] [3, 'c']
}
set.forEach((value,key)=>{
  console.log(key + ':' + value)  //1:'a'  2:'b'  3:'c'
})



Set的实例默认可遍历,默认迭代器生成函数是value,由此可以直接省略value,可以直接用for of

for(let item of set){
  console.log(item) //1 2 3
}


可默认遍历,默认迭代器生成函数是value()【需要验证】 set[Symbol.iterator] === set.value()

console.log(set[Symbol.iterator])   
> ƒ values() { [native code] }

console.log(Set.prototype[Symbol.iterator])  
> ƒ values() { [native code] }

console.log(Set.prototype[Symbol.iterator] === Set.prototype.value)
> false



可使用的方法:map、filter 因为set的默认迭代器生成函数式value是,所以有map、filter方法
let set = new Set([1,2,3,3,2])

使用场景
数组去重 (但不能去重包含对象和数组的数组)

const set = new Set([{a:1},{a:1}])
console.log(set)
> Set(2) {{…}, {…}}

const set1 = new Set([[1],[1],2,2,3])
console.log(set1)
> Set(4) {Array(1), Array(1), 2, 3}



并集、交集、差集

let set1 = new Set([2,3,4])
let set2 = new Set([3,4,5])

let union = [...set1,...set2]
let intersect = [...set1].filter((item)=> [...set2].includes(item))
let difference = [...set1].filter((item)=> ![...set2].includes(item))

console.log(union) 
> [2, 3, 4, 3, 4, 5]
console.log(new Set(union)) 
> Set(4) {2, 3, 4, 5}
console.log(intersect) 
> [3, 4]
console.log(new Set(intersect)) 
> Set(2) {3, 4}
console.log(difference) 
> [2]
console.log(new Set(difference))  
> Set(1) {2}


WeakSet
定义:允许将弱引用对象存储在集合中
实例方法:has、delete、add
WeakSet和Set的不同点:

WeakSet只存储引用对象(数组或者类似于数组对象),不能放基本类型;Set可以
WeakSet对象存储的对象值都是被弱引用的,即垃圾回收机制不考虑WeakSet对对象的引用,如果没有其他变量或者属性引用这个对象(会忽略该对象还存在于WeakSet中),则这个对象将会被垃圾回收掉。所以,WeakSet对象里有多少个成员元素,取决于垃圾回收机制有没有运行,运行前后成员个数可能不一致,遍历🔚结束之后,有的城规院可能取不到(被垃圾回收了)。
WeakSet对象是不可遍历的(不可枚举的),也就是没办法拿到它包含的所有元素。
没有clear实例方法,否则报错Uncaught TypeError: set1.clear is not a function

let set1 = new WeakSet([{a:1}])
console.log(set1)
> WeakSet {{…}}

let set1 = new WeakSet([{a:1},1])
console.log(set1)
> Uncaught TypeError: Invalid value used in weak set
    at WeakSet.add (<anonymous>)
    at new WeakSet (<anonymous>)


所以只能存储数组或者类似于数组

var ws = new WeakSet();
var obj = {};
ws.add(obj)
ws.add(window)



不能对对象和数组去重

const set = new WeakSet([{a:1},{a:1}])
console.log(set)
> WeakSet {{…}, {…}}

const set1 = new WeakSet([[1],[1]])
console.log(set1)
> WeakSet {Array(1), Array(1)}



Map
定义
Map是字典数据结构

使用场景
键作为对象时(比如键为DOM元素对象) 对象的键是字符串,如果是对象也会变成字符串,'[object Object]',这样键类型转换都是'[object Object]',得到的值一样肯定会出错

const data = {};
const divele = document.getElementByTagName('div');
data[divele] = '123';

data[divele]  // '123'
data['[object HTMLCollection]']  //123

const spanele = document.getElementByTagName('span');
data[spanele]  // '123'    结果易错


所以发挥map的作用,键可以是对象

与其他数据结构相互转换

Map与数组互换:
把数组放入Map参数中

const arr = [[{'k':1},'hhy'],['age',18]]
cosnt map = new Map(arr)

[...map]
Array.from(map)



Map与对象互换: 使用Object.keys和let [item,key] of map
JSON转换为Map:可以先转换成数组或者对象,再转换
特点
Map的键是跟内存地址绑定的,只要内存地址不一样,就视为两个键,这解决了同名属性碰撞(crash)的问题。
引出使用场景:使用扩展别人库的时候,如果使用对象或者数组作为键名,就不用担心自己的属性和原作者的属性同名。

const map = new Map();
map.set(['a'],111)
console.log(map.get(['a']))  //undefined


如果Map的键是简单数据类型(数字、字符串、布尔值,NaN除外),两个键只要是严格相等的,就视为一个键
0和-0是一个键
undefined和null不是同一个键
NaN不严格相等自身,但是视为同一个键
true和'true'不是同一个键

let map = new Map();
map.set(-0, 123)
map.get(+0) //123



Map可以接受的参数:任何具有Iterator接口、且每个成员都是一个双元素的数组的数据结构,比如数组、Set结构、Map结构
const set = new Set([2,3,4])

const m1 = new Map(set)
console.log(m1.get(2)) 
> Uncaught TypeError: Iterator value 2 is not an entry object
    at new Map (<anonymous>)

const set = new Set([{a: 1}])
const m1 = new Map(set)
console.log(m1.get(a)) 
> Uncaught ReferenceError: a is not defined

const set = new Set([{'a': 1}])
const m1 = new Map(set)
console.log(m1.get('a')) 
> undefined

const set = new Set([['a', 1]])
const m1 = new Map(set)
console.log(m1.get('a'))    
> 1

const map = new Map([['a', 1]])
const m1 = new Map(map)
console.log(m1.get('a'))    
> 1


注意:

a必须加引号,不然会报错Uncaught ReferenceError: a is not defined;
每个成员一定是双元素的数组,不能是对象等其他形式
方法和属性
实例属性constructor:构造函数
size: 返回字典中所包含的元素个数

let map = new Map([['name','hhy'],['age',18]])
map.size  //2



实例方法set(key,value)

get(key)
has(key)
delete(key)
clear()
let map = new Map([['name','hhy'],['age',18]])
console.log(map)
> Map(2) {"name" => "hhy", "age" => 18}

map.clear() 
console.log(map)
> Map(0) {}



遍历方法: keys():将字典中包含的所有键名以迭代器形式返回 values():将字典中包含的值以迭代器形式返回 entries():返回所有成员的迭代器 forEach():遍历字典的所有成员

let map = new Map([['name','hhy'],['age',18]])
map.entries() 
> MapIterator {"name" => "hhy", "age" => 18}

map.keys()
> MapIterator {"name", "age"}

map.values()
> MapIterator {"hhy", 18}

map.forEach((item,key)=>consoel.log(item,key))
> hhy name
  18 "age"

for(let [key,value] of map.entries()){
  console.log(key,value)
}
> hhy, name
  18, "age"
等同于使用map.entries() 
for(let [key,value] of map){
  console.log(key,value)
}
> hhy, name
  18, "age"



注:Map结构的默认遍历器接口(Symbole.iterator属性),就是entries方法

map[Symbol.iterator]
> ƒ entries() { [native code] }

map[Symbol.iterator] === map.entries
> true



Map和其他数据结构转换
Map结构转为数组结构,比较快速的方法是使用扩展运算符(...)

WeakMap
可以接受的参数:任何具有Iterator接口、且每个成员都是一个双元素的数组的数据结构,比如数组、Set结构、Map结构
只能接收对象(数组或者对象)为键名(null除外)
WeakMap对象是一组键值对的集合,其中的键是弱引用对象,而值可以是任意的。
WeakMap对象存储的对象值都是被弱引用的,即垃圾回收机制不考虑WeakMap对对象的引用,如果没有其他变量或者属性引用这个对象(会忽略该对象还存在于WeakMap中),则这个对象将会被垃圾回收掉。
WeakMap的key是不可枚举的。
实例方法(没有clear)set(key,value)
get(key)
has(key)
delete(key)

const k1 = {'a':1}
const k2 = [1,2]
const m1 = new WeakMap([[k1,2],[k2,22]])
console.log(m1.get(k1))
> 2
console.log(m1.get(k2))
> 22

const k3 = '[1,2]'
const m1 = new WeakMap([[k3,2]])
console.log(m1.get(k3))
> Uncaught TypeError: Invalid value used as weak map key
    at WeakMap.set (<anonymous>)
    at new WeakMap (<anonymous>)



可见键名只能是对象类型

使用场景DOM节点作为键名
保留私有数据

const _counter = new WeakMap();
const _action = new WeakMap();

class Countdown {
  constructor(counter, action) {
    _counter.set(this, counter);
    _action.set(this, action);
  }
  
  dec() {
    let counter = _counter.get(this);
    counter--;
    _counter.set(this, counter);
    if (counter === 0) {
      _action.get(this)();
    }
  }
}

let invoked = false;

const countDown = new Countdown(3, () => invoked = true);
countDown.dec();
countDown.dec();
countDown.dec();

console.log(`invoked status: ${invoked}`)
以上代码中,_counter 和 _action 用于存储私有属性的值,不会造成内存泄漏

 

 

Set和Map区别
共同点:集合、字典可以存储不重复的值

不同点:集合是以[value,value]的形式存储元素,字典是以[key,value]的形式存储

 

WeakSet和WeakMap区别
共同点:集合、字典可以存储不重复的值

不同点:集合是以[value,value]的形式存储元素,字典是以[key,value]的形式存储

 

总结
Set:

成员唯一、无序且不重复
元素是键和键值都是一致的[value,value],也可以说只有键值,没有键名。
可遍历,其方法有keys()、value()、entries()、forEach()
可使用其他方法:filter、map
实例方法:has、delete、add、clear
可以接受一个数组(或者具有iterable接口的其他数组结构)作为参数,用来初始化
使用场景:数组去重(不能对对象和数组去重)、并集、交集、差集
WeakSet

成员都是对象
成员都是弱引用,成员对象可以被垃圾回收,可以用来保存DOM节点,不容易造成内存泄漏
没有size属性,不可以遍历
实例方法:has、delete、add
使用场景:适合临时存放一组对象和跟对象绑定的信息(用于存储DOM节点,而不用担心这些节点从文档移除时会引发内存泄露)
Map元素是键值对[key,value]字典
可以遍历,其方法有keys()、value()、entries()、forEach()
实例方法:has、delete、set、get、clear
可以接受的参数:任何具有Iterator接口、且每个成员都是一个双元素的数组的数据结构,比如数组、Set结构、Map结构
使用场景:对象作为键名
WeakMap只接受对象类型(数组或者对象)作为键名(null除外),不接收其他类型作为键名
键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的
没有size属性,不可以遍历
实例方法:has、delete、set、get
使用场景:DOM节点作为键名、保留私有数据
字典类似于对象,对象比字典的好处是键可以是对象。

总之Set和Map主要的应用场景:数据重组、数据存储

 

作者:hannie76327
来源:掘金

分类
标签
已于2020-9-6 11:04:48修改
收藏
回复
举报
回复
    相关推荐