深入JavaScript高级语法-coderwhy分享学习
作者:互联网
### download:深入JavaScript高级语法-coderwhy
在读 Vue 3 响应式原理局部代码的过程中看到其在停止响应式处置的时分,为每个对象运用 WeakMap
创立了一个「缓存区」,代码如下:
// 留意下面这句代码! 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 }
从上面的代码能够看出,WeakMap
缓存区的作用就是用来避免对象被反复代理。
为什么 Vue 3 运用 WeakMap
来缓存代理对象?为什么不运用其他的方式来停止缓存,比方说 Map
?
什么是 WeakMap
WeakMap
对象是一组键值对的汇合,其中的键是 弱援用 的。其键必需是 对象,而值能够是恣意的。
语法
new WeakMap([iterable])
Iterable
是一个数组(二元数组)或者其他可迭代的且其元素是键值对的对象。每个键值对会被加到新的 WeakMap
里。
办法
WeakMap
有四个办法:分别是 get
、set
、has
、delete
,下面我们看一下其大致的用法:
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
为什么要用 WeakMap
而不是 Map
在 JavaScript 里,map
API 能够经过四个 API 办法共用两个数组(一个寄存键,一个寄存值)来完成。这样在给这种 map
设置值时会同时将键和值添加到这两个数组的末尾。从而使得键和值的索引在两个数组中相对应。当从该 map
取值的时分,需求遍历一切的键,然后运用索引从存储值的数组中检索出相应的值。
但这样的完成会有两个很大的缺陷,首先赋值和搜索操作都是 O(n)
的时间复杂度(n
是键值对的个数),由于这两个操作都需求遍历整个数组来停止匹配。
另外一个缺陷是可能会招致 内存走漏,由于数组会不断援用着每个键和值。这种援用使得 渣滓回收算法不能回收处置他们,即便没有其他任何援用存在了。
let jser = { name: "dachui" }; let array = [ jser ]; jser = null; // 掩盖援用
上面这段代码,我们把一个对象放入到数组中,那么只需这个数组存在,那么这个对象也就存在,即便没有其他对该对象的援用。
let jser = { name: "dachui" }; let map = new Map(); map.set(jser, ""); jser = null; // 掩盖援用
相似的,假如我们运用对象作为常规 Map
的键,那么当 Map
存在时,该对象也将存在。它会占用内存,并且不会被渣滓回收机制回收。
相比之下,原生的 WeakMap
持有的是每个键对象的 弱援用,这意味着在没有其他援用存在时渣滓回收能正确停止。
正是由于这样的弱援用,WeakMap
的 key
是不可枚举的 (没有办法能给出一切的 key
)。假如 key
是可枚举的话,其列表将会受渣滓回收机制的影响,从而得到不肯定的结果。因而,假如你想要这品种型对象的 key
值的列表,你应该运用 Map
。
综上,我们能够得出以下结论:WeakMap 的键所指向的对象,不计入渣滓回收机制。
所以,假如你要往对象上添加数据,又不想干扰渣滓回收机制,就能够运用 WeakMap
。
看到这里大家就应该晓得了,Vue 3 之所以运用 WeakMap
来作为缓冲区就是为了能将 不再运用的数据停止正确的渣滓回收。
什么是弱援用
关于「弱援用」,维基百科给出了答案:
在计算机程序设计中,弱援用 与 强援用 相对,是指不能确保其援用的对象不会被渣滓回收器回收的援用。一个对象若只被弱援用所援用,则被以为是不可访问(或弱可访问)的,并因而 可能在任何时辰被回收。
为什么会呈现弱援用
那么,为什么会呈现弱援用呢?弱援用除了能处理上述问题之外还能处理什么问题呢?要想答复这些问题,我们首先需求理解一下 V8
引擎是如何停止渣滓回收的。
关于 JSer
来说,内存的管理是自动的、无形的,这一切都归功于 V8
引擎在背后默默地帮我们找到不需求运用的内存并停止清算。
那么,当我们不再需求某个东西时会发作什么,V8
引擎又是如何发现并清算它的呢?
如今各大阅读器通常用采用的渣滓回收有两种办法,一种是「援用计数」,另外一种就是「标志肃清」。下面我们来看一下:
标志肃清
标志肃清被称为 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);
上面代码中,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
能够看到最后都输出 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
运用 Symbol
固然完成了而且正确输出了 3
、4
,但是我们发现能够在外界不经过 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
如上,圆满处理~~
标签:set,对象,JavaScript,WeakMap,语法,let,援用,new,coderwhy 来源: https://blog.csdn.net/HeYWVBvRmg/article/details/122289638