四、Symbol的使用(for in/of 的补充)及Set/WeakSet,Map/WeakMap的使用,强引用/弱引用
# 1、Symbol类型用法
# 1.1.Symbol的基本使用
Symbol是什么呢?Symbol是ES6中新增的一个基本数据类型,翻译为符号。
那么为什么需要Symbol呢?
- 在ES6之前,对象的属性名都是字符串形式,那么很容易造成属性名的冲突;
- 比如原来有一个对象,我们希望在其中添加一个新的属性和值,但是我们在不确定它原来内部有什么内容的情况下,很容易造成冲突,从而覆盖掉它内部的某个属性;
- 比如我们前面在讲apply、call、bind实现时,我们有给其中添加一个fn属性,那么如果它内部原来已经有了fn属性了呢?
- 比如开发中我们使用混入,那么混入中出现了同名的属性,必然有一个会被覆盖掉;
Symbol就是为了解决上面的问题,用来生成一个独一无二的值。
- Symbol值是通过Symbol函数来生成的,生成后可以作为属性名;
- 也就是在ES6中,对象的属性名可以使用字符串,也可以使用Symbol值;
Symbol即使多次创建值,它们也是不同的:Symbol函数执行后每次创建出来的值都是独一无二的;
我们也可以在创建Symbol值的时候传入一个描述description:这个是ES2019(ES10)新增的特性;
- 当然也可以通过Symbol值的description方法来获取描述信息
// 1.ES6中Symbol的基本使用 Symbol是一个函数,是ES6新增的基本数据类型
const s1 = Symbol()
const s2 = Symbol()
console.log(s1 === s2)//false
console.log(typeof Symbol)//function
// 2.在创建Symbol值的时候,Symbol函数可以传入一个参数:描述(description),
const s3 = Symbol("aaa")
console.log(s3.description)///用Symbol值的description方法 获取Symbol值的描述:aaa
2
3
4
5
6
7
8
9
# 1.2.Symbol作为属性名
- 我们通常会使用Symbol在对象中表示唯一的属性名:
// 1. Symbol值作为对象的属性名(key)
// 1.1. 在定义对象字面量时使用
const s1 = Symbol('1')
const s2 = Symbol('2')
const s3 = 'basketball'
const obj = {
[s1]: "abc"
}
// 1.2. 新增对象属性
obj[s2] = 'cba'
obj[s3] = "nba"
console.log(obj)//{ basketball: 'nba', [Symbol(1)]: 'abc', [Symbol(2)]: 'cba' }
// 1.3. 通过Object.key(obj) / for in等... 拿到obj对象所有属性的属性名;
// 我们发现通过Object.key(obj) / for in等... 拿不到属性名是Symbol值的 属性名
console.log(Object.keys(obj))//[ 'basketball' ]
for(const key in obj) {
console.log(key)// basketball
}
console.log(Object.getOwnPropertyNames(obj))//[ 'basketball' ]
// 1.4. 那我们可以用这个Object类方法来获取,以解决上面的问题:Object.getOwnPropertySymbols(obj)
console.log(Object.getOwnPropertySymbols(obj))//[ Symbol(1), Symbol(2) ]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 1.3.Object.getOwnPropertySymbols(obj)
- 使用Symbol值作为对象的属性名, 通过Object.keys/for in等... 是获取不到这些属性名为Symbol值 的属性名的;
- 需要用Object.getOwnPropertySymbols(obj)来获取对象中所有属性名是Symbol值 的属性名
// 1.使用Symbol值作为对象的属性名, 通过Object.keys/for in等... 是获取不到这些属性名为Symbol值 的属性名的;
const s1 = Symbol('1')
const s2 = Symbol('aaa')
const s3 = 'basketball'
const obj = {
[s1]: "abc"
}
obj[s2] = 'cba'
obj[s3] = "nba"
console.log(obj)//{ basketball: 'nba', [Symbol(1)]: 'abc', [Symbol(aaa)]: 'cba' }
// 需要Object.getOwnPropertySymbols(obj)来获取对象中所有属性名是Symbol值的属性名
const symbolKeys = Object.getOwnPropertySymbols(obj)
console.log(symbolKeys) //[ Symbol(1), Symbol(aaa) ]
for (const sKey of symbolKeys) {
console.log(sKey) // Symbol(1) Symbol(aaa)
console.log(obj[sKey]) // abc cba
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 1.4.相同值的Symbol
- 前面我们讲Symbol的目的是为了创建一个独一无二的值,那么如果我们现在就是想创建相同的Symbol应该怎么来做呢?
- 我们可以使用Symbol.for方法来做到这一点(也就是用Symbol.for方法来创建Symbol值:传入相同的描述,得到相同的Symbol值);
- 并且我们可以通过Symbol.keyFor方法 来获取 通过Symbol.for方法创建的Symbol值 对应的description 描述;(当然我们也可以用Symbol值的description方法来获取描述)
- 但是我们不能通过Symbol.keyFor方法 来获取 通过Symbol函数直接 创建的Symbol值对应的 description 描述,否则它将返回undefined
const s1 = Symbol('aaa')
const s2 = Symbol('bbb')
const obj = {
[s1]: 'nba',
[s2]: 'cba'
}
console.log(s1 === s2)//false
console.log(s1.description)//aaa
//1. Symbol.for方法
const sForKey1 = Symbol.for(s1.description)
console.log(sForKey1 === s1)//false
const sForKey2 = Symbol.for(s1.description)
console.log(sForKey1 === sForKey2)//true
const sForKey3 = Symbol.for('aaa')
console.log(sForKey2 === sForKey3)//true
//2. Symbol.keyFor方法(拿到)
const sc = Symbol.keyFor(sForKey1)
console.log(sc)//aaa
//当然我们也可以用Symbol值的description方法来获取描述
console.log(sForKey1.description)//aaa
// 但是我们不能通过Symbol.keyFor方法 来获取通过Symbol函数直接 创建的Symbol值的描述,否则它将返回undefined
console.log(Symbol.keyFor(s1))//undefined
const sForKey4 = Symbol.for(sc)
console.log(sForKey1 === sForKey4)//true
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 1.5.for in/for of
- for in
- 可用来枚举对象的属性值;
- 如果枚举的是数组,则拿到的是数组的索引值
const obj = {
name:'kobe',
age:39
}
const arr = ['nba','cba','ncaa']
for(const key in obj) {
console.log(typeof key)// string string 这里拿到的key是字符串,所以obj.key会报错,我们需要用到计算属性名写法:obj[key]
console.log(key)// name age
console.log(obj[key])// kobe 39
}
for(const key in arr) {
console.log(typeof key)// string string string
console.log(key)// 0 1 2
console.log(arr[key])// nba cba ncaa
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- for of
- 用来遍历可迭代的数据结构(即有迭代器的数据结构);如:数组,Set数据结构,Map数据结构,元素子元素的对象集合等...
- 注意:WeakSet数据结构跟WeakMap数据结构不可以用for of 进行遍历,因为他们是不可迭代对象
- 对象不可用for of进行遍历,因为对象是不可迭代对象
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
<script>
const obj = {
name:'kobe',
age:39
}
const arr = ['nba','cba','ncaa']
const set = new Set(arr)
//报错 :obj is not iterable
/*for(const item of obj) {
console.log(item)
}*/
for(const item of arr) {
console.log(item)//nba cba ncaa
}
for(const item of set) {
console.log(item)//nba cba ncaa
}
var liEls = document.querySelector('ul').children
console.log(liEls)//HTMLCollection(4) [li, li, li, li]
for(const item of liEls) {
console.log(item)
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 2、新增数据结构Set/WeakSet的使用
# 2.1.Set的了解与使用 -> 数组去重
在ES6之前,我们存储数据的结构主要有两种:数组、对象。
- 在ES6中新增了另外两种数据结构:Set、Map,以及它们的另外形式WeakSet、WeakMap。
Set是一个新增的数据结构,可以用来保存数据,类似于数组,但是和数组的区别是元素不能重复。
- 创建Set我们需要通过Set构造函数(暂时没有字面量创建的方式)
- Set内可存放任意类型
- Set对元素的引用是强引用
我们可以发现Set中存放的元素是不会重复的,那么Set有一个非常常用的功能就是给数组去重。
// 1.创建Set结构 set中相同元素只会存在一个(即set中的元素是不能重复的,重复的只会存在一个)
const set = new Set()
set.add(10)
set.add(20)
set.add(40)
set.add(333)
set.add(10)
console.log(set) //Set(4) { 10, 20, 40, 333 }
// 2. 数组去重 ES6方法
const arr = [1,2,3,4,5,3,2,9]
const set1 = new Set(arr)
console.log(set1) //Set(6) { 1, 2, 3, 4, 5, 9 }
const newArray1 = [...set1]//set1转成数组;
//Array.from() 方法对一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。
const newArray2 = Array.from(set1)//set1转成数组;
console.log(newArray1,newArray2)//[ 1, 2, 3, 4, 5, 9 ] [ 1, 2, 3, 4, 5, 9 ]
// 3. 对数组去重(去除重复的元素)ES5方法之一 补充:
const arr1 = [33, 10, 26, 30, 33, 26,10]
//indexOf() 方法可返回某个指定的 字符串值/数组元素 在 字符串/数组 中首次出现的位置。
// 如果没有找到匹配的 字符串值/数组元素 则返回 -1。找到了则返回索引值
const newArr1 = []
for (const item of arr1) {
if (newArr1.indexOf(item) === -1) newArr1.push(item)
}
console.log(newArr1)//[ 33, 10, 26, 30 ]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- Set数据结构中,添加对象(引用类型)出现的特殊情况
// 1. Set数据结构中,添加对象(引用类型)出现的特殊情况
// 1.1 提出问题:set中不能存在相同的元素,但是这里为什么两个空对象一样却可以同时存在呢?
//因为set的add方法添加的元素是两个不同的对象。在内存中都指向不同的内存地址,所以这两个空对象,不是同一个对象,所有才会同时存在
const set = new Set()
set.add({})
set.add({})
console.log(set)//Set(2) { {}, {} }
// 1.2. 提出问题:这里的obj 两次添加到set中,为什么只添加了一个呢
//因为set的add方法添加的元素都是obj对象,而obj引用的是空对象在堆内存中的内存地址。即每次添加的都是同一个对象(同一内存地址),所以不会同时存在,会被去重
const set1 = new Set()
const obj = {}
set1.add(obj)
set1.add(obj)
console.log(set1)//Set(1) { {} }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 2.2.Set的常见属性和方法
Set常见的实例属性:
- size:返回Set中元素的个数;
Set常用的实例方法:
- add(value):添加某个元素,返回值为:Set对象本身;
- delete(value):从set中删除和这个值相等的元素,返回值为:boolean类型;
- has(value):判断set中是否存在某个元素,返回值为:boolean类型;
- clear():清空set中所有的元素,没有返回值;
- forEach(callback,[, thisArg]):通过forEach遍历set;
- 另外Set是支持for of的遍历的
const arrSet = new Set([ 33, 10, 26, 30 ])
// 1. size属性:返回set对象的长度 Set.prototype.属性
console.log(arrSet.size)//4
// 2. Set的常用实例方法: Set.prototype.方法
// 2.1. add(value):添加set对象中的某个元素
arrSet.add(100)
console.log(arrSet)//Set(5) {33, 10, 26, 30, 100}
// 2.2. delete(value): 删除set对象中的某个元素
const flag = arrSet.delete(33)
console.log(arrSet,flag)//Set(4) {10, 26, 30, 100} true
// 2.3. has(value): 查找set对象中是否有某个元素,有则返回true
const flag1 = arrSet.has(100)
console.log(flag1)//true
// 2.4. 对Set实例化对象进行遍历
arrSet.forEach(item => console.log(item))// 10 26 30 100)
for (const item of arrSet) {
console.log(item)// 10 26 30 100
}
// 2.5. clear()方法: 清除set对象内部所有元素
arrSet.clear()
console.log(arrSet)//Set(0) {}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 2.3.WeakSet的了解与使用
- 和Set类似的另外一个数据结构称之为WeakSet,也是内部元素不能重复的数据结构。
- 那么和Set有什么区别呢?
- 区别一:WeakSet中只能存放对象类型,不能存放基本数据类型;
- 区别二:WeakSet对元素的引用是弱引用,如果没有其他引用对某个对象进行引用,那么GC可以对该对象进行回收;(2.5有讲强引用,弱引用的区别)
const weakSet = new WeakSet()
const set = new Set()
let obj = {
name: "why"
}
// 1.WeakSet和Set的区别一: WeakSet只能存放对象类型
// weakSet.add(10) // TypeError: Invalid value used in weak set 报错
// 2.WeakSet和Set的区别二: WeakSet对对象是一个弱引用
// 强引用和弱引用的概念(2.5有讲强引用,弱引用的区别)
set.add(obj)// 建立的是强引用
weakSet.add(obj)// 建立的是弱引用
2
3
4
5
6
7
8
9
10
11
12
13
# 2.4.WeakSet常见方法
WeakSet没有size实例属性
WeakSet常见的实例方法:
- add(value):添加某个元素,返回值为:WeakSet对象本身;
- delete(value):从WeakSet中删除和这个值相等的元素,返回值为:boolean类型;
- has(value):判断WeakSet中是否存在某个元素,返回值为:boolean类型;
- 没有clear方法,也不可遍历
为什么WeakSet不能遍历?
- 因为WeakSet只是对对象的弱引用,如果我们遍历获取到其中的元素,那么有可能造成对象不能正常的销毁。
- 所以存储到WeakSet中的对象是没办法获取的;
const arr = [{ name: 'kobe' }, { name: 'kobe' }]
const weakSet1 = new WeakSet(arr)
console.log(weakSet1)//WeakSet {{…}, {…}}
const obj = { name: 'lyk' }
// 1. add(value)方法: 添加一个元素,返回值为:调用实例方法的实例对象本身
weakSet1.add(obj)
console.log(weakSet1)//WeakSet {{…}, {…}, {…}}
// 2. delete(value)方法: 删除一个元素,删除成功则返回:true
const flag = weakSet1.delete(obj)
console.log(flag)//true
// 3. has(value)方法: 查找weakSet1对象中是否有某个元素,有则返回true
const flag1 = weakSet1.has(obj)
console.log(flag1)//false
//4. WeakSet没有实例属性size, 及clear实例方法;也不可遍历
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 2.5.WeakSet的应用
- 那么这个东西(WeakSet)有什么用呢?
- 事实上这个问题并不好回答,我们来使用一个Stack Overflow上的答案;
// 1.WeakSet的应用场景
//需求:只想用我们new出来的实例对象来调用我们类中的方法,其他则报错,即是这里用call指定this值调,会报错:p.running.call({name: "why"})
const personSet = new WeakSet()
class Person {
constructor(name, age) {
this.name = name,
this.age = age,
personSet.add(this)// 将根据构造函数Person,new出来的实例对象,添加到personSet中
}
running() {
if (!personSet.has(this)) {// 检查调用该方法时的this指向,是否是Person类的实例化对象,如果是则正常调用,如果不是则抛出错误
throw new Error(`${this.name}不是通过Person类new出来的实例化对象,所以不可调用Person类的实例方法:running方法`)
}
console.log(`我是Person类的实例对象${this.name},所以可以正常调用Person的实例方法:running~`)
}
}
const p = new Person('p', 18)
const p1 = new Person('p1', 20)
const obj = { name: "obj" }
console.log(personSet)//WeakSet {Person, Person} 这里的两个Person对象分别是p和p1
p.running()//我是Person类的实例对象p,所以可以正常调用Person的实例方法:running~
p.running.call(p1)//我是Person类的实例对象p1,所以可以正常调用Person的实例方法:running~
p.running.call(obj)//Uncaught Error(报错内容): obj不是通过Person类new出来的实例化对象,所以不可调用Person类的实例方法:running方法
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 2.6.强引用跟弱引用的理解
- 建立的是强引用:Set,Map
- 建立的是弱引用:WeakSet,WeakMap
- 当obj和obj1都设置为null后;会出现如下图表现:
- 下面我们用具体代码来还原一下上面图解的案例吧;不过我们这里需要借助一下FinalizationRegistry这个类,当对象被垃圾回收器回收时会请求回调;
- FinalizationRegistry类的MDN文档学习 (opens new window)
<script>
let obj = { name: 'lyk' }
let set1 = new Set()
set1.add(obj)
let obj1 = { name: 'lyk' }
let weakSet1 = new WeakSet()
weakSet1.add(obj1)
const finalRegistry = new FinalizationRegistry((value) => {
console.log(value, '对象被回收了')
console.log(set1)
console.log(weakSet1)
})
finalRegistry.register(obj, 'obj')//注册的目标对象为:obj;如果对象被回收则会请求回调new FinalizationRegistry(callback)中的回调函数callback -> callback()
finalRegistry.register(obj1, 'obj1')//注册的目标对象为:obj1; 与上同理,
obj = null
obj1 = null
//过一会,会打印如下:
// obj1 对象被回收了
// Set(1) {{…}} -> 发现set1中添加进来的对象还存在,说明该对象没有被垃圾回收器回收,说明Set对元素的引用是强引用
// WeakSet {} -> 发现weakSet1中添加进来的对象不存在了,说明该对象被垃圾回收器回收了,说明WeakSet对元素的引用是弱引用
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- 通过上面案例我们已经对强引用跟弱引用有很深入的理解了,下面我们继续来学习另一种数据结构:Map/WeakMap吧
# 3、新增数据结构Map/WeakMap的使用
# 3.1.Map的了解与使用
另外一个新增的数据结构是Map,用于存储映射关系。
但是我们可能会想,在之前我们可以使用对象来存储映射关系,他们有什么区别呢?
事实上我们对象存储映射关系只能用字符串(ES6新增了Symbol)作为属性名(key);
某些情况下我们可能希望通过其他类型作为key,比如对象,这个时候会自动将对象转成字符串来作为key;
Map中键值对的key可以是任意类型
那么我们就可以使用Map:
// 1.JavaScript中对象中是不能使用对象来作为属性名(key)的
const obj1 = { name: "kobe" }
const obj2 = { name: "james" }
const info = {//这里当你用对象作为对象中的key时,对象都会被 计算属性(计算属性名写法) 计算成: [object Object];最终导致属性名相同,后者会覆盖前者的属性值
[obj1]: "aaa",// 相当于:'[object Object]':'aaa'
[obj2]: "bbb"// 相当于:'[object Object]':'bbb'
}
console.log(info)// { '[object Object]': 'bbb' }
// 2.Map就是允许我们对象类型(及任意类型)来作为key的 内部是以键值对形式存在的
// {[key,value],[key,value],[key,value]......}
// 2.1. Map中添加键值对写法一
const map1 = new Map()
map1.set(obj1,'abc')
map1.set(obj2,'cba')
map1.set('1','ncaa')
console.log(map1.get(obj1))//abc
console.log(map1.get(obj2))//cba
console.log(map1.get('1'))//ncaa
console.log(map1)//Map(3) {{ name: 'kobe' } => 'abc',{ name: 'james' } => 'cba','1' => 'ncaa'}
// 2.2. Map中添加键值对写法二
const map2 = new Map([
[obj1,'abc'],
[obj2,'cba'],
[obj1,'nba']
])
console.log(map2.get(obj1))//nba
console.log(map2.get(obj2))//cba
// 2.3. 补充:Map创建时,直接传入参数(键值对)写法测试
// console.log(new Map(obj1,'curry').get(obj1)) //报错:对象不可迭代(无法读取属性符号(Symbol.iterator))
// console.log(new Map([obj1,'curry']).get(obj1)) //报错:迭代器值curry不是条目对象
console.log(new Map([[obj1,'curry']]).get(obj1))//curry
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
- Map中键值对的key 对对象的引用是强引用
- Map和WeakMap中键值对的value 对对象的引用都是强引用(通过3.5 测试得出结论)
# 3.2.Map的常用属性和方法
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进行遍历。
Map内部是以键值对形式存在的,Map允许我们对象类型(其他类型也可以)来作为key(即允许对象类型(及其他类型)来作为key)
const obj1 = {name:'james'}
const obj2 = {name:'curry'}
const map1 = new Map([[obj1, "aaa"], [obj2, "bbb"], [2, "ddd"]])
// 1.常见的属性: size 返回元素的个数
console.log(map1.size) //3
// 2.常见的方法
// 2.1. set(key,value) 在map对象中添加一个键值对,并返回对象本身
map1.set("kobe", "eee")
console.log(map1)//Map(4) {{…} => 'aaa', {…} => 'bbb', 2 => 'ddd', 'kobe' => 'eee'}
// 2.2. get(key) //传入key,获取value值
const value = map1.get("kobe")
console.log(value)//eee
// 2.3. has(key) 判断是否包括某一个key; 如果有则返回true
const flag = map1.has('kobe')
console.log(flag)//true
// 2.4. delete(key) 删除相对应key的键值对; 删除成功则返回true
var flag1 = map1.delete("kobe")
console.log(flag1)//true
console.log(map1)//Map(3) {{…} => 'aaa', {…} => 'bbb', 2 => 'ddd'}
// 2.5.遍历map
map1.forEach((value, key) => {
console.log(key,value)
//{name: 'why'} aaa
//{name: 'kobe'} bbb
//2 ddd
})
for(const item of map1) {
console.log(item)
//[ { name: 'james' }, 'aaa' ]
//[ { name: 'curry' }, 'bbb' ]
//[ 2, 'ddd' ]
}
// 2.6. clear 清除Map对象中所有键值对
map1.clear()
console.log(map1)//Map(0) {}
const map3 = new Map([['name1','lyk']])
for (const item of map3) {
console.log(item)// ['name1', 'lyk']
console.log(item[0], item[1])// name1 lyk
}
for (const [key, value] of map3) {//对数组进行解构const [key,value] = [2,'ddd']
console.log(key, value)//name1 lyk
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 3.3.WeakMap的了解与使用
- 和Map类型的另外一个数据结构称之为WeakMap,也是以键值对的形式存在的。
- 那么和Map有什么区别呢?
- 区别一:WeakMap的key只能使用对象,不接受其他的类型作为key;
- WeakMap内部是以键值对形式存在的,WeakMap不允许我们使用基本数据作为key(即只允许对象类型来作为key)
- 区别二:WeakMap中键值对的key 对对象的引用是弱引用,如果没有其他引用引用这个对象,那么GC可以回收该对象;
- Map和WeakMap中键值对的value 对对象的引用都是强引用(通过3.5 测试得出结论)
- 区别一:WeakMap的key只能使用对象,不接受其他的类型作为key;
// 1.WeakMap和Map的区别二: WeakMap弱引用, Map强引用
const obj = {name: "obj1"}
const map = new Map()
const weakMap = new WeakMap()
map.set(obj, "aaa")
weakMap.set(obj, "aaa")
// 2.区别一: key不能使用基本数据类型 ,只能使用引用类型 即对象类型来作为属性名(key)
weakMap.set(1,'cba')//报错:用作弱映射键的值无效
2
3
4
5
6
7
8
9
# 3.4.WeakMap常见方法
WeakMap没有size实例属性
WeakMap常见的实例方法有四个:
- set(key,value):在Map中添加key、value,并且返回整个Map对象;
- get(key):根据key获取Map中的value;
- has(key):判断是否包括某一个key,返回Boolean类型;
- delete(key):根据key删除一个键值对,返回Boolean类型;
- 没有clear()实例方法,也不可以遍历
const obj = { name: "james" }
const obj1 = { name: 'kobe' }
const weakMap1 = new WeakMap([[obj1, 'ccc']])
// 1. WeakMap常用的实例方法(4种)
// 1.1. set(key,value)方法: 在Map中添加key、value键值对,并且返回整个Map对象本身;
weakMap1.set(obj, "aaa")
console.log(weakMap1)//WeakMap {{…} => 'aaa', {…} => 'ccc'}
// 1.2. get(key)方法:根据key获取Map中的value
console.log(weakMap1.get(obj))//aaa
// 1.3. has(key)方法:判断是否包括某一个key,包含则返回true
const flag = weakMap1.has(obj)
console.log(flag)//true
// 1.4. delete(key)方法 :根据key删除一个键值对,删除成功则返回true
const flag1 = weakMap1.delete(obj)
console.log(flag1)//true
console.log(weakMap1)//WeakMap {{…} => 'ccc'}
//4.WeakMap没有实例属性size, 及clear实例方法;也不可遍历
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 3.5.强引用和弱引用(Map/WeakMap案例)
<script>
let obj = { name: 'obj' }
let value = {value:'value'}
let map1 = new Map()
map1.set(obj,value)
let obj1 = { name: 'obj1' }
let value1 = {value:'value1'}
let weakMap1 = new WeakMap()
weakMap1.set(obj1,value1)
const finalRegistry = new FinalizationRegistry((value) => {
console.log(value, '对象被回收了')
console.log(map1)
console.log(weakMap1)
})
finalRegistry.register(obj, 'obj')
finalRegistry.register(obj1, 'obj1')
obj = null
obj1 = null
//过一会,会打印如下:
//obj1 对象被回收了
// Map(1) {{…} => {…}}
// WeakMap {}
// 测试用:
// finalRegistry.register(value, 'value')
// finalRegistry.register(value1, 'value1')
// value = null
// value1 = null
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 3.6.应用:Vue响应式原理中的WeakMap使用
// 应用场景(vue3响应式原理)
const obj1 = {
name: "why",
age: 18
}
function obj1NameFn1() {
console.log("obj1NameFn1被执行")
}
function obj1NameFn2() {
console.log("obj1NameFn2被执行")
}
function obj1AgeFn1() {
console.log("obj1AgeFn1")
}
function obj1AgeFn2() {
console.log("obj1AgeFn2")
}
const obj2 = {
name: "kobe",
height: 1.88,
address: "广州市"
}
function obj2NameFn1() {
console.log("obj1NameFn1被执行")
}
function obj2NameFn2() {
console.log("obj1NameFn2被执行")
}
// 1.创建WeakMap
const weakMap = new WeakMap()
// 2.收集依赖结构
// 2.1.对obj1收集的数据结构
const obj1Map = new Map()
obj1Map.set("name", [obj1NameFn1, obj1NameFn2])
obj1Map.set("age", [obj1AgeFn1, obj1AgeFn2])
weakMap.set(obj1, obj1Map)
// 2.2.对obj2收集的数据结构
const obj2Map = new Map()
obj2Map.set("name", [obj2NameFn1, obj2NameFn2])
weakMap.set(obj2, obj2Map)
// 3.如果obj1.name发生了改变 执行下面1,2,3 的操作
// Proxy/Object.defineProperty
obj1.name = "james"
const targetMap = weakMap.get(obj1) //1 获取weakMap中key为obj1的值 即obj1Map
const fns = targetMap.get("name")//2 获取targetMap(obj1Map)中key为name的值,即[obj1NameFn1, obj1NameFn2]
fns.forEach(item => item())//3 对里面相应函数进行调用,实现响应式(后续讲vue源码会详细说,这里知道一个大概流程即可)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 4、Set/WeakSet和 Map/WeakMap总结
# 4.1.Set和WeakSet
- Set对象有size,add,delete,has,clear,forEach等方法,可遍历,可存放任意类型(强引用)
- WeakSet对象有add,delete,has等方法 ,没有size属性,没有clear,forEach方法,不可遍历 ,且只可存放对象类型(弱引用)
# 4.2.Map和WeakMap
Map有size,set,get,has,clear,delete,forEach等方法,可遍历;
Map实例化对象中键值对的key 对对象的引用是强引用
Map内部是以键值对形式存在的,Map允许对象类型(及其他类型即任意类型)来作为属性名(key)
WeakMap有set,get,has,delete等方法,没有size属性,没有clear,forEach方法,不可遍历;
WeakMap实例化对象中键值对的key 对对象的引用是弱引用
WeakMap内部也是以键值对形式存在的,WeakMap不允许我们使用基本数据作为key(即只允许对象类型来作为属性名(key))
# 5、ES6其他知识点说明
事实上ES6(ES2015)是一次非常大的版本更新,所以里面重要的特性非常多:
除了前面讲到的特性外还有很多其他特性;
- Proxy、Reflect,我们会在后续专门进行学习。
- 并且会利用Proxy、Reflect来讲解Vue3的响应式原理;
nPromise,用于处理异步的解决方案
- 后续会详细学习;
- 并且会学习如何手写Promise;
ES Module模块化开发:
- 从ES6开发,JavaScript可以进行原生的模块化开发;
- 这部分内容会在工程化部分学习;
- 包括其他模块化方案:CommonJS、AMD、CMD等方案;
下面我们先来学学ES7(ES2016)- ES13(ES2022)的一些内容吧