四、Symbol的使用(for in/of 的补充)及Set/WeakSet,Map/WeakMap的使用,强引用/弱引用

2022/8/5 ES6Symbol类型SetWeakSetMapWeakMap强引用弱引用模拟Vue响应式原理

# 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
1
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) ]
1
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
}
1
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
1
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
}
1
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>
1
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 ]
1
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) { {} }
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) {}
1
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)// 建立的是弱引用
1
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实例方法;也不可遍历
1
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方法
1
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

LZv8jU.png

  • 当obj和obj1都设置为null后;会出现如下图表现:

vnJnAJ.png

<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>
1
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 
1
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
}
1
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 测试得出结论)
// 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')//报错:用作弱映射键的值无效
1
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实例方法;也不可遍历
1
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>
1
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源码会详细说,这里知道一个大概流程即可)
1
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)的一些内容吧

最后更新时间: 2022/08/06, 00:48:27
彩虹
周杰伦