码迷,mamicode.com
首页 > 其他好文 > 详细

如何理解WeakMap?

时间:2021-06-28 18:19:55      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:就是   tor   垃圾回收   doc   asc   result   今天   https   cached   

而且JavaScript既然已经有了Map类型的数据结构,为什么还有一种叫做WeakMap类型的数据结构呢?它和垃圾回收有什么关系?

WeakMap很早之前就遇到过,但是没有系统学习过,今天就来对它一探究竟。

 

初识WeakMap

WeakMap对象是一组键值对的集合,其中key是弱引用的
WeakMap的key必须是对象类型,value可以是任意类型

WeakMap的key为什么是弱引用的?

弱引用的意义:如果是作为key的对象没有任何地方引用它的话,垃圾收集器(GC)会将其标记为目标并且进行垃圾回收

WeakMap的key和value可以是哪些类型

key:必须是任意object类型(对象、数组、Map、WeakMap等等)
value:any(任意类型,所以也包括undefined,null)

WeakMap与Map最大的不同

WeakMap的key是不可枚举的,而Map是可枚举的。
不可枚举就意味着获取不到WeakMap的key列表。

设计为不可枚举的原因是因为:如果枚举WeakMap的key,那就需要依赖垃圾回收器(GC)的状态,从而引入不确定性。

 

新增WeakMap类型是为什么?

map API在js中可以通过共享4个API(get,set,has,delete)的两个数组来实现:一个存储key,一个存储value。在这个map上设置元素同步推入一个key和一个value到数组尾部。作为结果,key和value的索引会和两个数组绑定起来。从map获取一个值得话,会遍历所有key去找到一个匹配的,然后使用这个匹配到的index从values数组中查询到对应的值。

这样实现的话会有2个主要的弊端:

首先是set和search的时间复杂度是O(n),n是map中key数组的数量,因为都需要遍历列表去查找到需要的值
其次是会造成内存泄漏,因为数组需要无期限地去确保每个key和每个value的引用。这些引用会导致阻止key被垃圾回收掉,即使这个对象没有任何地方再引用到了,key对应的value也同样会被阻止垃圾回收。

相比之下,原生的WeakMap会保持对key的“弱”引用。原生的WeakMap不会阻止垃圾回收,最终会移除对key对象的引用。“弱”引用同样可以让value很好地垃圾回收。WeakMap特别适用于key映射的信息只有不被垃圾回收时才有价值的场景,换句话说就是WeakMap适用于动态垃圾回收key的场景。

因为引用是弱的,所以WeakMap的键是不能枚举的。没有方法去获取key的列表。如果枚举WeakMap的key,那就需要依赖垃圾回收器(GC)的状态,从而引入不确定性。如果必须要有key的话,应该去使用Map。

 

WeakMap的基本概念

语法

new WeakMap()
new WeakMap(iterable)

其中iterable是数组或者任意可以迭代的对象,需要拥有key-value对(一般是一个二维数组)。null会被当做undefined。

iterable为二维数组
const iterable = [
    [{foo:1}, 1], 
    [[1,2,3], 2], 
    [window, 3]
]
const iwm = new WeakMap(iterable)
// WeakMap {{…} => 1, Window => 3, Array(3) => 2}

实例方法

WeakMap.prototype.delete(key)

删除key关联的任意值。删除后WeakMap.prototype.has(key)返回false。

WeakMap.prototype.get(key)

返回与key关联的值,假设不存在关联值得话返回undefined。

WeakMap.prototype.has(key)

返回key在WeakMap上是否存在的结果。

WeakMap.prototype.set(key, value)

在WeakMap对象上为对应key设置指定的value。并且返回WeakMap对象

 

WeakMap最简使用方式

const wm1 = new WeakMap(),
      wm2 = new WeakMap(),
      wm3 = new WeakMap();
const o1 = {},
      o2 = function() {},
      o3 = window;

wm1.set(o1, 37);
wm1.set(o2, ‘azerty‘);
wm2.set(o1, o2); // WeakMap的值可以是任意类型,包括object和function
wm2.set(o3, undefined);
wm2.set(wm1, wm2); // key和value可以是任意对象。包括WeakMap!

wm1.get(o2); // "azerty"
wm2.get(o2); // undefined, wm2上没有o2这个key
wm2.get(o3); // undefined, 因为这是设置的值

wm1.has(o2); // true
wm2.has(o2); // false
wm2.has(o3); // true (即使value是undefined)

wm3.set(o1, 37);
wm3.get(o1); // 37

wm1.has(o1); // true
wm1.delete(o1);
wm1.has(o1); // false

 

WeakMap存储私有数据

实例和原型链上的数据和方法是公开的,所以可以通过WeakMap类型的私有变量去隐藏实现细节。

const privates = new WeakMap();

function Public() {
  const me = {
    // Private data goes here
  };
  privates.set(this, me);
}

Public.prototype.method = function () {
  const me = privates.get(this);
  // Do stuff with private data in `me`...
};

module.exports = Public;

 

拥有clear方式的WeakMap类

class ClearableWeakMap {
  constructor(init) {
    this._wm = new WeakMap(init);
  }
  clear() {
    this._wm = new WeakMap();
  }
  delete(k) {
    return this._wm.delete(k);
  }
  get(k) {
    return this._wm.get(k);
  }
  has(k) {
    return this._wm.has(k);
  }
  set(k, v) {
    this._wm.set(k, v);
    return this;
  }
}
const key1 = {foo:1};
const key2 = [1,2,3];
const key3 = window;
const cwm = new ClearableWeakMap([
    [key1, 1], 
    [key2, 2], 
    [key3, 3]
])
cwm.has(key1) // true
console.log(cwm);// ClearableWeakMap {_wm: WeakMap {Window => 3, {…} => 1, Array(3) => 2}}
cwm.clear(); // 垃圾回收当前WeakMap,并且声称新的空WeakMap
cwm.has(key1) // false
console.log(cwm);// ClearableWeakMap {_wm: WeakMap {}}

https://www.98891.com/article-53-1.html

WeakMap式自动垃圾回收缓存函数

实现缓存函数的方式有很多种,比如单次缓存,Map式全量缓存,LRU最近最少缓存等等。
那么为什么还需要WeakMap式的缓存函数呢?这是因为入参为对象类型的缓存且方便浏览器垃圾回收。

缓存函数实现

function memoizeWeakMap(fn) {
  const wm = new WeakMap();
  return function (arg) {
    if (wm.has(arg)) {
      return wm.get(arg);
    }
    const cachedArg = arg;
    const cachedResult = fn(arg);
    wm.set(cachedArg, cachedResult)
    console.log(‘weakmap object‘, wm)
    return cachedResult;
  };
}

let testFn = (bar) => {return Object.prototype.toString.call(bar)}; // 这里需要改造一下,改造完返回传入对象的类型

let memoizeWeakMapFn = memoizeWeakMap(testFn);

memoizeWeakMapFn(document) // weakmap对document生成缓存
memoizeWeakMapFn([1,2,3]) // weakmap对[1,2,3]生成缓存
memoizeWeakMapFn(function(){}) // weakmap对function(){}生成缓存

memoizeWeakMapFn(new WeakMap())  // weakmap对WeakMap实例生成缓存
memoizeWeakMapFn(new Map()) // weakmap对Map实例生成缓存
memoizeWeakMapFn(new Set())  // weakmap对Set实例生成缓存

WeakMap:
0: {Array(3) => "[object Array]"}
1: {function(){} => "[object Function]"}
2: {WeakMap => "[object WeakMap]"}
3: {Map(0) => "[object Map]"}
4: {#document => "[object htmlDocument]"}
5: {Set(0) => "[object Set]"}

如何体现出WeakMap的垃圾回收特性呢

// 忽略部分代码同上
setTimeout(()=>{
    memoizeWeakMapFn(document)    
},5000)

此时有时最后一次weakmap的打印结果如下:

WeakMap:
0: {#document => "[object htmlDocument]"}
为什么说是“有时”?

因为打印时垃圾回收可能并没有执行完成,虽然会带来不确定性,但是可以确定的是,假设对象没有再被引用,WeakMap中的key会被浏览器自动垃圾回收掉。

为什么weakmap中仅保存了document?

这是因为[1,2,3], function(){},new WeakMap(),new Map(),new Set()在后面都没有再继续引用了,而且因为它们作为了WeakMap的key,所以会被浏览器自动垃圾回收掉。

如何不让key被垃圾回收掉呢?

保持一个变量对它的引用。

let memoizeWeakMapFn = memoizeWeakMap(testFn);
let retainArray = [1,2,3]; // 保持引用避免被垃圾回收
let retainMap = new Map(); // 保持引用避免被垃圾回收

memoizeWeakMapFn(document) // weakmap对document生成缓存
memoizeWeakMapFn(retainArray) // weakmap对[1,2,3]生成缓存
memoizeWeakMapFn(function(){}) // weakmap对function(){}生成缓存

memoizeWeakMapFn(new WeakMap())  // weakmap对WeakMap实例生成缓存
memoizeWeakMapFn(retainMap) // weakmap对Map实例生成缓存
memoizeWeakMapFn(new Set())  // weakmap对Set实例生成缓存

setTimeout(()=>{
    memoizeWeakMapFn(document)    
},5000)

此时打印结果为:

WeakMap:
0: {#document => "[object HTMLDocument]"}
1: {Map(0) => "[object Map]"}
2: {Array(3) => "[object Array]"}

这是因为[1,2,3], new Map()被变量retainArray和retainMap持续引用着,所以不会被垃圾回收。而function(){},new WeakMap(),new Set()都没有再继续引用了,而且因为它们作为了WeakMap的key,所以会被浏览器自动垃圾回收掉。

如果手动触发垃圾回收呢?

可以借助Chrome DevTools的memory面板工具,有一个手动触发垃圾回收的按钮。
技术图片

// ...
setTimeout(()=>{
    memoizeWeakMapFn(document)    
},5000)

比如在上面的例子中,设置了一个5秒的延时:只要代码运行后的5秒内,去手动触发“垃圾回收按钮”,就可以很精确地看到WeakMap的key被垃圾回收了。

当然5秒这个时间是可以人为调整的,保证自己能在setTimeout内的代码运行前触发对WeakMap的垃圾回收即可,可以适当调大。

如何理解WeakMap?

标签:就是   tor   垃圾回收   doc   asc   result   今天   https   cached   

原文地址:https://www.cnblogs.com/Qooo/p/14933374.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!