Home > Web Front-end > Vue.js > What is WeakMap? Why is it used as a cache area in Vue3 responsive source code?

What is WeakMap? Why is it used as a cache area in Vue3 responsive source code?

青灯夜游
Release: 2021-12-22 10:20:30
forward
2603 people have browsed it

This article will take you to understand WeakMap in Vue3 and introduce why WeakMap is used as the "cache area" in Vue 3 responsive source code. I hope it will be helpful to everyone.

What is WeakMap? Why is it used as a cache area in Vue3 responsive source code?

[Related recommendation: "vue.js Tutorial"]

Watch while reading some code of Vue 3 responsive principle When it is performing responsive processing, a "cache area" is created for each object using WeakMap. The code is as follows:

// 注意下面这句代码!
const reactiveMap = new WeakMap();

// 核心进行劫持的方法  处理 get 和 set 的逻辑
const mutableHandlers = {
    get,
    set
}

function reactive(target: object) {
    return createReactiveObject(target, mutableHandlers, reactiveMap);
}

/**
 * @description 创建响应式对象 
 * @param {Object} target 需要被代理的目标对象
 * @param {Function} baseHandlers 针对每种方式对应的不同处理函数
 * @param {Object} proxyMap WeakMap 对象
 */
function createReactiveObject(target, baseHandlers, proxyMap) {
    // 检测 target 是不是对象,不是对象直接返回,不进行代理
    if (!isObject(target)) {
        return target
    }
    const existsProxy = proxyMap.get(target);
    // 如果该对象已经被代理过了,则直接返回,不进行重复代理
    if (existsProxy) {
        return existsProxy
    }
    // 未被代理过,则创建代理对象
    const proxy = new Proxy(target,baseHandlers);
    // 缓存,避免重复代理,即避免 reactive(reactive(Object)) 的情况出现
    proxyMap.set(target,proxy); 
    return proxy
}
Copy after login

As can be seen from the above code, WeakMap The purpose of the cache area is to prevent objects from being repeatedly proxied.

Why does Vue 3 use WeakMap to cache proxy objects? Why not use other methods for caching, such as Map?

What is WeakMap

WeakMap The object is a collection of key-value pairs, where the keys are weak references. The keys must be objects, and the values ​​can be arbitrary.

Syntax

new WeakMap([iterable])
Copy after login

Iterable is an array (two-element array) or other iterable object whose elements are key-value pairs. Each key-value pair will be added to a new WeakMap.

Method

WeakMap There are four methods: get, set, has , delete, let’s take a look at its general usage:

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); // value 可以是任意值,包括一个对象或一个函数
wm2.set(o3, undefined);
wm2.set(wm1, wm2); // 键和值可以是任意对象,甚至另外一个 WeakMap 对象

wm1.get(o2); // "azerty"
wm2.get(o2); // undefined,wm2 中没有 o2 这个键
wm2.get(o3); // undefined,值就是 undefined

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

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

wm1.has(o1);   // true
wm1.delete(o1);
wm1.has(o1);   // false
Copy after login

Why use WeakMap instead of Map

In JavaScript, the map API can be implemented through four API methods sharing two arrays (one for keys and one for values). In this way, when setting a value to this map, the key and value will be added to the end of both arrays at the same time. This makes the key and value indexes correspond in the two arrays. When getting a value from this map, you need to iterate through all the keys and then use the index to retrieve the corresponding value from the array where the value is stored.

But such an implementation will have two big shortcomings. First, the assignment and search operations have a time complexity of O(n) (n is the key number of value pairs), because both operations require traversing the entire array to match.

Another disadvantage is that it may cause memory leak, because the array will always reference each key and value. Such references prevent the garbage collection algorithm from recycling them, even if no other references exist.

let jser = { name: "dachui" };

let array = [ jser ];

jser = null; // 覆盖引用
Copy after login

In the above code, we put an object into the array, then as long as the array exists, then the object will also exist, even if there are no other references to the object.

let jser = { name: "dachui" };

let map = new Map();
map.set(jser, "");

jser = null; // 覆盖引用
Copy after login

Similarly, if we use an object as a key for a regular Map, then the object will exist when the Map exists. It takes up memory and will not be reclaimed by the garbage collection mechanism.

In contrast, the native WeakMap holds a weak reference for each key object, which means that garbage collection can Do it correctly.

It is precisely because of such weak references that key of WeakMap is not enumerable (no method can give all key) . If key is enumerable, its list will be affected by the garbage collection mechanism, resulting in undefined results. Therefore, if you want a list of key values ​​for this type of object, you should use a Map.

To sum up, we can draw the following conclusion: The object pointed to by the key of WeakMap is not counted in the garbage collection mechanism.

So, if you want to add data to the object and don't want to interfere with the garbage collection mechanism, you can use WeakMap.

Everyone should know after seeing this, the reason why Vue 3 uses WeakMap as a buffer is to correctly garbage collect the data that is no longer used.

What is a weak reference

Regarding "weak reference", Wikipedia gives the answer:

In computer programming, Weak reference and Strong Reference Relative, refers to a reference that cannot ensure that the object it refers to will not be recycled by the garbage collector. An object is considered inaccessible (or weakly accessible) if it is only referenced by weak references, and therefore may be recycled at any time.

Why do weak references appear?

So, why do weak references appear? In addition to solving the above problems, what other problems can weak references solve? To answer these questions, we first need to understand how the V8 engine performs garbage collection.

For JSer, memory management is automatic and invisible, all thanks to the V8 engine silently helping us find the files we don’t need to use behind the scenes. memory and clean it up.

So, what happens when we no longer need something, V8 How does the engine find it and clean it up?

现在各大浏览器通常用采用的垃圾回收有两种方法,一种是「引用计数」,另外一种就是「标记清除」。下面我们来看一下:

标记清除

标记清除被称为 mark-and-sweep,它是基于 可达性 来判断对象是否存活的,它会定期执行以下「垃圾回收」步骤:

  • 垃圾收集器找到所有的根,并标记(记住)它们。

  • 然后它遍历并标记来自它们的所有引用。所有被遍历到的对象都会被记住,以免将来再次遍历到同一个对象。

  • ……如此操作,直到所有可达的(从根部)引用都被访问到。

  • 没有被标记的对象都会被删除。

我们还可以将这个过程想象成从根溢出一个巨大的油漆桶,它流经所有引用并标记所有可到达的对象,然后移除未标记的。

引用计数

引用计数方式最基本的形态就是让每个被管理的对象与一个引用计数器关联在一起,该计数器记录着该对象当前被引用的次数,每当创建一个新的引用指向该对象时其计数器就加 1,每当指向该对象的引用失效时计数器就减 1。当该计数器的值降到 0 就认为对象死亡。

区别

引用计数与基于「可达性」的标记清除的内存管理方式最大的区别就是,前者只需要 局部的信息,而后者需要 全局的信息

在引用计数中每个计数器只记录了其对应对象的局部信息 —— 被引用的次数,而没有(也不需要)一份全局的对象图的生死信息。

由于只维护局部信息,所以不需要扫描全局对象图就可以识别并释放死对象。但也因为缺乏全局对象图信息,所以 无法处理循环引用 的状况。

所以,更高级的引用计数实现会引入 弱引用 的概念来打破某些已知的循环引用。

WeakMap 应用

存储 DOM 节点

WeakMap 应用的典型场合就是以 DOM 节点作为键名。下面是一个例子。

const myWeakmap = newWeakMap();
myWeakmap.set(
  document.getElementById('logo'),
  { timesClicked: 0 },
);
document.getElementById('logo').addEventListener('click', () => {
  const logoData = myWeakmap.get(document.getElementById('logo'));
  logoData.timesClicked++;
}, false);
Copy after login

上面代码中,document.getElementById('logo') 是一个 DOM 节点,每当发生 click 事件,就更新一下状态。我们将这个状态作为值放在 WeakMap 里,对应的键就是这个节点对象。一旦这个 DOM 节点删除,该状态就会自动消失,不存在内存泄漏风险。

数据缓存

谜底就在谜面上,文章一开头我们提出的问题就是这里的答案。Vue 3 在实现响应式原理的时候就是使用了 WeakMap 来作为响应式对象的「缓存区」。

关于这一点用法也很简单,当我们需要关联对象和数据,比如在不修改原有对象的情况下储存某些属性或者根据对象储存一些计算的值等,而又不想手动去管理这些内存问题的时候就可以使用 WeakMap

部署类中的私有属性

WeakMap 的另一个用处是部署类中的私有属性。

值得一提的是,TypeScript 中已经实现的 private 私有属性原理就是利用 WeakMap

私有属性应该是不能被外界访问到,不能被多个实例共享,JavaScript 中约定俗成地使用下划线来标记私有属性和方法,一定程度来说是不靠谱的。

下面我们用三种方法来实现:

  • 版本一:闭包
const testFn = (function () {
  let data;

  class Test {
    constructor(val) {
      data = val
    }
    getData() {
      return data;
    }
  }
  return Test;
})();

let test1 = new testFn(3);
let test2 = new testFn(4);
console.log(test1.getData()); // 4
console.log(test2.getData()); // 4
Copy after login

可以看到最后都输出 4,多实例共享私有属性了,所以版本一不符合。

  • 版本二:Symbol
const testFn = (function () {
  let data = Symbol('data')

  class Test {
    constructor(val) {
      this[data] = val
    }
    getData() {
      return this[data]
    }
  }
  return Test;
})();

let test1 = new testFn(3);
let test2 = new testFn(4);
console.log(test1.getData()); // 3
console.log(test2.getData()); // 4

console.log(test1[Object.getOwnPropertySymbols(test1)[0]]); // 3
console.log(test2[Object.getOwnPropertySymbols(test2)[0]]); // 4
Copy after login

使用 Symbol 虽然实现了而且正确输出了 34,但是我们发现可以在外界不通过 getData 方法直接拿到私有属性,所以这种方法也不满足我们的要求。

  • 版本三:WeakMap
const testFn = (function () {
  let data = new WeakMap()

  class Test {
    constructor(val) {
      data.set(this, val)
    }
    getData() {
      return data.get(this)
    }
  }
  return Test;
})();

let test1 = new testFn(3);
let test2 = new testFn(4);
console.log(test1.getData()); // 3
console.log(test2.getData()); // 4
Copy after login

如上,完美解决~~

更多编程相关知识,请访问:编程入门!!

The above is the detailed content of What is WeakMap? Why is it used as a cache area in Vue3 responsive source code?. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:segmentfault.com
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template