Set
在ES6之前,我们存储数据的结构主要有两种:数组、对象。
在ES6中新增了另外两种数据结构:Set、Map,以及它们的另外形式WeakSet、WeakMap。
Set是一个新增的数据结构,可以用来保存数据,类似于数组,但是和数组的区别是元素不能重复。
创建 Set 我们需要通过 Set 构造函数(暂时没有字面量创建的方式):
我们可以发现 Set 中存放的元素是不会重复的,那么 Set 有一个非常常用的功能就是给数组去重。
// 1.创建Set结构
const set = new Set()
set.add(10)
set.add(20)
set.add(10) // 重复数据添加不上
// 2.添加对象时特别注意:
set.add({})
set.add({}) // 这是两个对象,添加成功,因为内存地址不一样
const obj = { name: 'zs'}
set.add(obj)
set.add(obj) // 这是同一个对象,添加失败
console.log(set) // Set(5) { 10, 20, {}, {}, { name: 'zs' } }
// 3.对数组去重(去除重复的元素)
// 之前手动去重
const arr = [33, 10, 26, 30, 33, 26]
const newArr = []
for (const item of arr) {
if (newArr.indexOf(item) !== -1) {
newArr.push(item)
}
}
// 现在利用 set 转存一下就自动去重
const arrSet = new Set(arr) // 构造函数可接收可迭代对象
// 去重后的数组重新存入新数组中
const newArr1 = Array.from(arrSet) // 数组构造函数的 from 也能接收可迭代对象
// Set 支持展开运算符,可展开存入新数组
const newArr2 = [...arrSet]
console.log(newArr1) // [ 33, 10, 26, 30 ]
console.log(newArr2) // [ 33, 10, 26, 30 ]
Set 常见的属性:
size
:返回Set中元素的个数;
Set 常用的方法:
add(value)
:添加某个元素,返回Set对象本身;delete(value)
:从set中删除和这个值相等的元素,返回boolean类型;has(value)
:判断set中是否存在某个元素,返回boolean类型;clear()
:清空set中所有的元素,没有返回值;forEach(callback, [, thisArg])
:通过forEach遍历set;
另外 Set 是支持for o
f的遍历的。
// 创建Set结构
const arrSet = new Set()
arrSet.add(10)
arrSet.add(20)
arrSet.add(30)
// 1.size属性
console.log(arrSet.size) // 3
// 2.Set的方法
// add
arrSet.add(100)
console.log(arrSet) // Set(4) { 10, 20, 30, 100 }
// delete
arrSet.delete(20)
console.log(arrSet) // Set(3) { 10, 30, 100 }
// has
console.log(arrSet.has(100)) // true
// 3.对Set进行遍历
arrSet.forEach(item => {
console.log(item) // 10 30 100
})
for (const item of arrSet) {
console.log(item) // 10 30 100
}
// clear
arrSet.clear()
console.log(arrSet) // Set(0) {}
WeakSet
和 Set 类似的另外一个数据结构称之为 WeakSet,也是内部元素不能重复的数据结构。
weakSet 相比 Set 最大的不同就是它只能存放引用类型数据,并且这个引用是弱引用。
弱引用和强引用
首先我们知道 GC 回收垃圾,会从根开始判断对象是不是可达的。强引用就是可达的,而弱引用,GC 认为是不可达的。所以当一个对象只有弱引用去引用它,没有强引用引用,GC 会认为这是垃圾,需要回收。
WeakSet常见的方法:
add(value)
:添加某个元素,返回WeakSet对象本身;delete(value)
:从WeakSet中删除和这个值相等的元素,返回boolean类型;has(value)
:判断WeakSet中是否存在某个元素,返回boolean类型; ```javascript const weak = new WeakSet()
const arr = [1, 2, 4] let obj = {name: ‘zs’} // obj 强引用 {name: ‘zs’}
// weak.add(10) // Invalid value used in weak set weak.add(arr) weak.add(obj)
console.log(weak.has(arr)) // true console.log(weak.delete(arr)) // true console.log(weak.has(arr)); // false
// 断开强引用,{name: ‘zs’} 对象将会被 GC 回收 obj = null // 虽然 weak 集合中也指向(浅拷贝)了 {name: ‘zs’},但这是个弱引用
<a name="bdKFF"></a>
## WeakSet 的使用场景
WeakSet 的最大的能力就是这个弱引用,也就是我想要一个集合,里面存放对象,然后不想用这个对象了,希望回收。一般状态下,需要去断开对象其他的强引用后还要去集合中断开对对象的引用。如果这个集合是 WeakSet,集合对集合元素的引用都是弱引用,所以就不用手动去集合中断开这一步操作了。
调用对象中的方法强制只能使用该类的实例化对象来调用:
```javascript
const personSet = new WeakSet()
class Person {
constructor() {
personSet.add(this) // new实例化的时候就将this放入集合中,this就代表实例化对象
}
running() {
// 调用方法的时候就判断下当前调用的对象是否是new 出来的对象
if (!personSet.has(this)) {
throw new Error("不能通过非构造方法创建出来的对象调用running方法")
}
console.log("running~", this)
}
}
let p = new Person()
p.running()
// this放入集合,为什么要选WeakSet?
p = null
personSet.delete(p) // 因为当需要回收对象的时候,可以不用写这步
// call 修改了this,调用running 方法的对象其实是{name: "why"},所以if判断不会通过
p.running.call({name: "why"})
Map
另外一个新增的数据结构是Map,用于存储映射关系。
对象也可以存储键值对,这和 Map 有啥区别?
最大的区别就是对象的 key 只能是字符串或者 Symbol,其他数据类型做 key,也会被转成字符串。
比如对象做 key,它就会被转成[object Object]
字符串。所有的对象都会被转成这同一个字符串,所以导致 value 覆盖。严格说引用类型在对象中就不能当做 key。
但是 Map 的 key,就不做要求,它是一个集合容器,允许所有数据类型成为 key。map 一般就是用来存对象。
const obj1 = {}
const obj2 = {}
const obj = {
[obj1]: 123, // []计算
[obj2]: 354
}
console.log(obj) //{ '[object Object]': 354 }
const map = new Map()
map.set(obj1, 123)
map.set(obj2, 456)
console.log(map); // Map(2) { {} => 123, {} => 456 }
Map常见的属性:
size
:返回Map中元素的个数;
Map常见的方法:
set(key, value)
:在Map中添加key、value,并且返回整个Map对象;get(key)
:根据key获取Map中的value;has(key)
:判断是否包括某一个key,返回Boolean类型;delete(key)
:根据key删除一个键值对,返回Boolean类型;clear()
:清空所有的元素;forEach(callback, [, thisArg])
:通过forEach遍历Map;
Map 也可以通过for of
进行遍历,但是它遍历出来是一个数组的形式,所以结合解构方便一点。
const obj1 = {}
const obj2 = {}
const obj = {
[obj1]: 123,
[obj2]: 354
}
console.log(obj) //{ '[object Object]': 354 }
const map = new Map()
// KV 添加进 map
map.set(obj1, 123) // key 为对象
map.set(obj2, 456)
map.set('obj1', 789) // 同名,但 key 是字符串
// 初始化时允许以数组的形式添加元素,注意:KV 也是数组包裹
const map2 = new Map([[obj1, "aaa"], [obj2, "bbb"], [2, "ddd"]])
console.log(map2)
console.log(map) // Map(3) { {} => 123, {} => 456, 'obj1' => 789 }
// map 长度
console.log(map.size); // 3
// 判断key是否在map中
console.log(map.has(obj1)); // true
console.log(map.has('obj1')); // true
// 通过 key 获取 value
console.log(map.get(obj1)); // 123
console.log(map.get('obj1')); // 789
// 删除键值对
console.log(map.delete(obj2)); // true
// 遍历 forEach
map.forEach(item => { // 只有一个参数,遍历的是 value
console.log(item)
})
map.forEach((key, value) => {
console.log(key, value)
})
// 遍历 for of
for (const item of map) {
console.log(item) // item 是个数组,想要某个kv,需要索引item[0]
}
for (const [key, value] of map) {
console.log(key, value) // {} 123, {} 456, obj1 789
}
// 清空map
map.clear()
console.log(map); //Map(0) {}
WeakMap
WeakMap 与 Map 的区别和 WeakSet、Set 相似。WeakMap 元素的 key 只能是引用类型,且为弱引用。WeakMap 也是不能遍历的,不能遍历也没有 size。
WeakMap常见的方法有四个:
set(key, value)
:在Map中添加key、value,并且返回整个Map对象;get(key)
:根据key获取Map中的value;has(key)
:判断是否包括某一个key,返回Boolean类型;delete(key)
:根据key删除一个键值对,返回Boolean类型;WeakMap 应用场景
应用 WeakMap 最大的原因肯定就是看中了它弱引用的特点,让对象能自动被回收。其中响应式原理中就有该应用。
响应式的本质就是监听到变化后就执行一系列渲染函数。将被监听的对象和要绑定的函数联系起来的就是 WeakMap。 ```javascript{{ name }}
const obj = { name: ‘zs’, age: 18 }
比如上文,我们会先把 obj 作为 key 放入 WeakMap 中,name,age 属性作为 key 放入到 map, name,age 属性渲染函数作为 value 和 key 绑定在一起。此时再把这个包含 obj 属性和渲染函数的 map 放入 WeakMap 中作为 obj key 的 value。
```javascript
const map = new Map([['name',[namefn1, namefn2]], ['age', [agefn1, agefn2]]]);
const weakMap = new WeakMap([[obj, map]])
obj.name = 'ls' // 监听到变化
weakMap.get(obj).get('name')() // 执行对应的渲染函数