ES6 系列之 WeakMap的使用示例

ES6 系列之 WeakMap的使用示例

 

这篇文章主要介绍了ES6 系列之 WeakMap的使用示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

 

前言

我们先从 WeakMap 的特性说起,然后聊聊 WeakMap 的一些应用场景。

 

特性

1. WeakMap 只接受对象作为键名

1

2

3

4

5

const map = new WeakMap();

map.set(1, 2);

// TypeError: Invalid value used as weak map key

map.set(null, 2);

// TypeError: Invalid value used as weak map key

2. WeakMap 的键名所引用的对象是弱引用

这句话其实让我非常费解,我个人觉得这句话真正想表达的意思应该是:

WeakMaps hold "weak" references to key objects,

翻译过来应该是 WeakMaps 保持了对键名所引用的对象的弱引用。

我们先聊聊弱引用:

在计算机程序设计中,弱引用与强引用相对,是指不能确保其引用的对象不会被垃圾回收器回收的引用。 一个对象若只被弱引用所引用,则被认为是不可访问(或弱可访问)的,并因此可能在任何时刻被回收。

在 JavaScript 中,一般我们创建一个对象,都是建立一个强引用:

1

var obj = new Object();

只有当我们手动设置 obj = null 的时候,才有可能回收 obj 所引用的对象。

而如果我们能创建一个弱引用的对象:

1

2

// 假设可以这样创建一个

var obj = new WeakObject();

我们什么都不用做,只用静静的等待垃圾回收机制执行,obj 所引用的对象就会被回收。

我们再来看看这句:

WeakMaps 保持了对键名所引用的对象的弱引用

正常情况下,我们举个例子:

1

2

3

4

const key = new Array(5 * 1024 * 1024);

const arr = [

 [key, 1]

];

使用这种方式,我们其实建立了 arr 对 key 所引用的对象(我们假设这个真正的对象叫 Obj)的强引用。

所以当你设置 key = null 时,只是去掉了 key 对 Obj 的强引用,并没有去除 arr 对 Obj 的强引用,所以 Obj 还是不会被回收掉。

Map 类型也是类似:

1

2

3

4

5

6

7

let map = new Map();

let key = new Array(5 * 1024 * 1024);

 

// 建立了 map 对 key 所引用对象的强引用

map.set(key, 1);

// key = null 不会导致 key 的原引用对象被回收

key = null;

我们可以通过 Node 来证明一下这个问题:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

// 允许手动执行垃圾回收机制

node --expose-gc

 

global.gc();

// 返回 Nodejs 的内存占用情况,单位是 bytes

process.memoryUsage(); // heapUsed: 4640360 ≈ 4.4M

 

let map = new Map();

let key = new Array(5 * 1024 * 1024);

map.set(key, 1);

global.gc();

process.memoryUsage(); // heapUsed: 46751472 注意这里大约是 44.6M

 

key = null;

global.gc();

process.memoryUsage(); // heapUsed: 46754648 ≈ 44.6M

 

// 这句话其实是无用的,因为 key 已经是 null 了

map.delete(key);

global.gc();

process.memoryUsage(); // heapUsed: 46755856 ≈ 44.6M

如果你想要让 Obj 被回收掉,你需要先 delete(key) 然后再 key = null:

1

2

3

4

5

let map = new Map();

let key = new Array(5 * 1024 * 1024);

map.set(key, 1);

map.delete(key);

key = null;

我们依然通过 Node 证明一下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

node --expose-gc

 

global.gc();

process.memoryUsage(); // heapUsed: 4638376 ≈ 4.4M

 

let map = new Map();

let key = new Array(5 * 1024 * 1024);

map.set(key, 1);

global.gc();

process.memoryUsage(); // heapUsed: 46727816 ≈ 44.6M

 

map.delete(key);

global.gc();

process.memoryUsage(); // heapUsed: 46748352 ≈ 44.6M

 

key = null;

global.gc();

process.memoryUsage(); // heapUsed: 4808064 ≈ 4.6M

这个时候就要说到 WeakMap 了:

1

2

3

4

const wm = new WeakMap();

let key = new Array(5 * 1024 * 1024);

wm.set(key, 1);

key = null;

当我们设置 wm.set(key, 1) 时,其实建立了 wm 对 key 所引用的对象的弱引用,但因为 let key = new Array(5 * 1024 * 1024) 建立了 key 对所引用对象的强引用,被引用的对象并不会被回收,但是当我们设置 key = null 的时候,就只有 wm 对所引用对象的弱引用,下次垃圾回收机制执行的时候,该引用对象就会被回收掉。

我们用 Node 证明一下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

node --expose-gc

 

global.gc();

process.memoryUsage(); // heapUsed: 4638992 ≈ 4.4M

 

const wm = new WeakMap();

let key = new Array(5 * 1024 * 1024);

wm.set(key, 1);

global.gc();

process.memoryUsage(); // heapUsed: 46776176 ≈ 44.6M

 

key = null;

global.gc();

process.memoryUsage(); // heapUsed: 4800792 ≈ 4.6M

所以 WeakMap 可以帮你省掉手动删除对象关联数据的步骤,所以当你不能或者不想控制关联数据的生命周期时就可以考虑使用 WeakMap。

总结这个弱引用的特性,就是 WeakMaps 保持了对键名所引用的对象的弱引用,即垃圾回收机制不将该引用考虑在内。只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。

也正是因为这样的特性,WeakMap 内部有多少个成员,取决于垃圾回收机制有没有运行,运行前后很可能成员个数是不一样的,而垃圾回收机制何时运行是不可预测的,因此 ES6 规定 WeakMap 不可遍历。

所以 WeakMap 不像 Map,一是没有遍历操作(即没有keys()、values()和entries()方法),也没有 size 属性,也不支持 clear 方法,所以 WeakMap只有四个方法可用:get()、set()、has()、delete()。

应用

1. 在 DOM 对象上保存相关数据

传统使用 jQuery 的时候,我们会通过 $.data() 方法在 DOM 对象上储存相关信息(就比如在删除按钮元素上储存帖子的 ID 信息),jQuery 内部会使用一个对象管理 DOM 和对应的数据,当你将 DOM 元素删除,DOM 对象置为空的时候,相关联的数据并不会被删除,你必须手动执行 $.removeData() 方法才能删除掉相关联的数据,WeakMap 就可以简化这一操作:

1

2

3

4

5

6

7

8

let wm = new WeakMap(), element = document.querySelector(".element");

wm.set(element, "data");

 

let value = wm.get(elemet);

console.log(value); // data

 

element.parentNode.removeChild(element);

element = null;

2. 数据缓存

从上一个例子,我们也可以看出,当我们需要关联对象和数据,比如在不修改原有对象的情况下储存某些属性或者根据对象储存一些计算的值等,而又不想管理这些数据的死活时非常适合考虑使用 WeakMap。数据缓存就是一个非常好的例子:

1

2

3

4

5

6

7

8

9

10

11

12

const cache = new WeakMap();

function countOwnKeys(obj) {

  if (cache.has(obj)) {

    console.log('Cached');

    return cache.get(obj);

  } else {

    console.log('Computed');

    const count = Object.keys(obj).length;

    cache.set(obj, count);

    return count;

  }

}

3. 私有属性

WeakMap 也可以被用于实现私有变量,不过在 ES6 中实现私有变量的方式有很多种,这只是其中一种:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

const privateData = new WeakMap();

 

class Person {

  constructor(name, age) {

    privateData.set(this, { name: name, age: age });

  }

 

  getName() {

    return privateData.get(this).name;

  }

 

  getAge() {

    return privateData.get(this).age;

  }

}

 

export default Person;

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: ES6 代理模式是 JavaScript 中一种用于控制对对象的访问的设计模式。它允计你定义一个中介对象来控制对其他对象的访问。 下面是一个简单的例子,展示了如何使用 ES6 中的 Proxy 构造函数来创建一个代理: ``` const target = {}; const handler = { get: (target, prop) => { return prop in target ? target[prop] : 'Not Found'; } }; const proxy = new Proxy(target, handler); console.log(proxy.a); // 'Not Found' ``` 在这个例子中,我们创建了一个空的目标对象 `target`,并定义了一个处理程序对象 `handler`,它包含了一个 `get` 拦截器函数。接下来,我们使用这个处理程序对象和目标对象来创建一个代理对象 `proxy`。 当我们访问代理对象的属性时,例如 `proxy.a`,会触发处理程序对象的 `get` 拦截器函数。在这个例子中,如果目标对象 `target` 中包含了该属性,则返回该属性的值,否则返回 'Not Found'。 希望这个示例能帮助你理解 ES6 代理模式的工作原理。 ### 回答2: ES6中的代理模式是一种设计模式,它允许我们创建一个对象(代理),该对象可以代替另一个对象进行操作,从而可以控制或者增强原始对象的行为。 一个常见的代理模式示例是对一个对象进行操作前后进行预处理或者后处理。比如,我们有一个对象person,代表一个人的信息,包括姓名和年龄。我们可以创建一个代理对象proxy,对获取和设置person对象的属性进行监控和处理。 首先,我们创建一个person对象,包含name和age属性: ``` let person = { name: 'Tom', age: 25 }; ``` 然后,我们创建一个代理对象proxy,通过使用`new Proxy(target, handler)`来创建代理。target参数是要代理的对象,handler参数是一个对象,包含一个或多个捕获器(traps),用于定义代理的行为。 ``` let proxy = new Proxy(person, { get(target, property) { console.log('获取属性:', property); return target[property]; }, set(target, property, value) { console.log('设置属性:', property, ' 值:', value); target[property] = value; } }); ``` 在上面的代码中,我们定义了get捕获器来处理对属性的获取操作,当获取属性时,会打印相应的信息,并返回目标对象上相应的属性值。我们还定义了set捕获器来处理对属性的设置操作,当设置属性时,会打印相应的信息,并将新的属性值赋给目标对象。 现在,我们可以通过访问proxy对象来获取和设置person对象的属性,代理对象会在获取和设置属性时触发相应的行为。比如: ``` console.log(proxy.name); // 输出:获取属性: name Tom proxy.age = 30; // 输出:设置属性: age 值: 30 console.log(proxy.age); // 输出:获取属性: age 30 ``` 通过代理对象,我们可以在获取和设置属性时进行额外的处理。这个示例只是代理模式的一个简单应用案例,实际中代理模式可以有更多更复杂的用途,例如权限控制、性能优化等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值