ES6 WeakMap的实际用途是什么?
ECMAScript 6中引入的WeakMap
数据结构的实际用途是什么?
由于弱映射的关键字会强烈引用其对应的值,因此,只要其关键字仍然存在,确保插入弱映射的值不会消失,因此不能用于备注表,caching或其他任何你通常使用弱引用,弱值的地图等。
在我看来,这是:
weakmap.set(key, value);
这只是一个迂回的说法:
key.value = value;
我错过了哪些具体的用例?
从根本上
WeakMaps提供了一种从外部扩展对象而不干扰垃圾收集的方法。 无论什么时候你想扩展一个对象,但由于它是封闭的,或者来自外部来源而不能扩展,可以应用一个WeakMap。
WeakMap是一个映射(字典),其中的键很弱 – 也就是说,如果对键的所有引用都丢失了,并且没有更多的值的引用 – 则该值可以被垃圾收集。 我们先通过例子来展示这个,然后解释一下,最后用真正的用法来完成。
比方说,我正在使用一个API给我一个特定的对象:
var obj = getObjectFromLibrary();
现在,我有一个使用该对象的方法:
function useObj(obj){ doSomethingWith(obj); }
我想跟踪一个特定的对象调用了多less次这个方法,并且如果它发生了N次以上的报告。 天真的人会认为使用地图:
var map = new Map(); // maps can have object keys function useObj(obj){ doSomethingWith(obj); var called = map.get(obj) || 0; called++; // called one more time if(called > 10) report(); // Report called more than 10 times map.set(obj, called); }
这个工作,但它有一个内存泄漏 – 我们现在跟踪传递给函数的每个库对象,使库对象从垃圾收集。 相反,我们可以使用WeakMap
:
var map = new WeakMap(); // create a weak map function useObj(obj){ doSomethingWith(obj); var called = map.get(obj) || 0; called++; // called one more time if(called > 10) report(); // Report called more than 10 times map.set(obj, called); }
内存泄漏消失了。
用例
一些会导致内存泄漏并由WeakMap
启用的用WeakMap
包括:
- 保留关于特定对象的私人数据,只能通过引用地图来访问个人数据。 私人标志提案将会出现一个更为特殊的方法,但从现在开始已经很长时间了。
- 保持关于库对象的数据而不改变它们或引起开销。
- 保留关于一小部分对象types的对象的数据不会导致隐藏类JS引擎用于相同types的对象的问题。
- 在浏览器中保存有关DOM节点等主机对象的数据。
- 从外部向对象添加function(如另一个答案中的事件发射器示例)。
让我们看看一个真正的用途
它可以用来从外部扩展一个对象。 让我们从io.js的真实世界中给出一个实用的(适应的,真实的 – 来expression一个观点)的例子。
假设你是NodeJS,并且你有Promise
对象 – 现在你想跟踪所有当前被拒绝的promise,但是你不想让它们不被垃圾回收,以防其他的引用存在。
现在,你不想为了显而易见的原因而将属性添加到本地对象 – 所以你被卡住了。 如果你保留引用的承诺,你会导致内存泄漏,因为没有垃圾收集可能发生 – 如果你不保留引用,你不能保存个别承诺的额外信息。 任何涉及保存承诺ID的scheme都意味着需要引用它。
inputWeakMaps
WeakMaps意味着键很弱。 没有办法列举一张弱地图或获取所有的值。 在一张弱地图中,你可以存储基于密钥的数据,当密钥被垃圾收集时,值也是如此。
这意味着,如果承诺可以存储有关状态的信息,那么这个对象仍然可以被垃圾回收。 后来,如果你得到一个对象的引用,你可以检查是否有任何有关的状态,并报告。
这被用来实现佩特卡·安东诺夫(Petka Antonov)的未处理的拒绝挂钩 :
process.on('unhandledRejection', function(reason, p) { console.log("Unhandled Rejection at: Promise ", p, " reason: ", reason); // application specific logging, throwing an error, or other logic here });
我们在地图上保存有关承诺的信息,并可以知道何时处理了被拒绝的承诺。
这个答案在现实世界的情况下似乎是有偏见和无法使用的。 请按原样阅读,不要将其视为实验以外的实际选项
一个用例可以用来作为听众的字典,我有一个同事谁这样做。 这是非常有帮助的,因为任何听众都直接以这种做事方式为目标。 再见listener.on
。
但是从更抽象的angular度来看, WeakMap
对于基本上任何东西的取消WeakMap
都是非常强大的,你不需要命名空间来隔离它的成员,因为它已经被这个结构的性质所暗示。 我敢肯定你可以做一些重大的内存改进,通过replaceawkwards多余的对象键(即使解构为你做的工作)。
在阅读下一步之前
我现在意识到我的强调并不是解决问题的最好方法,正如本杰明·格鲁恩鲍姆 ( Benjamin Gruenbaum)指出的那样(查看他的答案,如果它不在我的上面):这个问题不能用一个定期的Map
来解决,因为它会被泄露,所以WeakMap
的主要优点在于它不会干扰垃圾收集,因为它们没有参考。
这是我的同事的实际代码(感谢他分享)
这里完整的源代码是关于上面提到的听众pipe理(你也可以看一下规格 )
var listenableMap = new WeakMap(); export function getListenable (object) { if (!listenableMap.has(object)) { listenableMap.set(object, {}); } return listenableMap.get(object); } export function getListeners (object, identifier) { var listenable = getListenable(object); listenable[identifier] = listenable[identifier] || []; return listenable[identifier]; } export function on (object, identifier, listener) { var listeners = getListeners(object, identifier); listeners.push(listener); } export function removeListener (object, identifier, listener) { var listeners = getListeners(object, identifier); var index = listeners.indexOf(listener); if(index !== -1) { listeners.splice(index, 1); } } export function emit (object, identifier, ...args) { var listeners = getListeners(object, identifier); for (var listener of listeners) { listener.apply(object, args); } }
WeakMap适用于封装和信息隐藏
WeakMap只适用于ES 6及以上版本。 WeakMap是键和值对的集合,其中键必须是对象。 在下面的例子中,我们用两个项目构build一个WeakMap:
var map = new WeakMap(); var pavloHero = {first: "Pavlo", last: "Hero"}; var gabrielFranco = {first: "Gabriel", last: "Franco"}; map.set(pavloHero, "This is Hero"); map.set(gabrielFranco, "This is Franco"); console.log(map.get(pavloHero));//This is Hero
我们使用set()方法来定义一个对象和另一个项目(在我们的例子中是一个string)之间的关联。 我们使用get()方法来检索与对象关联的项目。 WeakMaps的一个有趣的方面是它对地图中的关键字有一个弱引用。 弱引用意味着如果对象被销毁,垃圾收集器将从WeakMap中移除整个条目,从而释放内存。
var TheatreSeats = (function() { var priv = new WeakMap(); var _ = function(instance) { return priv.get(instance); }; return (function() { function TheatreSeatsConstructor() { var privateMembers = { seats: [] }; priv.set(this, privateMembers); this.maxSize = 10; } TheatreSeatsConstructor.prototype.placePerson = function(person) { _(this).seats.push(person); }; TheatreSeatsConstructor.prototype.countOccupiedSeats = function() { return _(this).seats.length; }; TheatreSeatsConstructor.prototype.isSoldOut = function() { return _(this).seats.length >= this.maxSize; }; TheatreSeatsConstructor.prototype.countFreeSeats = function() { return this.maxSize - _(this).seats.length; }; return TheatreSeatsConstructor; }()); })()
弱地图可用于存储有关DOM元素的元数据,而不会干扰垃圾收集或使同事恼火您的代码。 例如,您可以使用它们对网页中的所有元素进行数字索引。
没有WeakMaps或WeakSets:
var elements = document.getElementsByTagName('*'), i = -1, len = elements.length; while (++i !== len) { // Production code written this poorly makes me want to cry: elements[i].lookupindex = i; elements[i].elementref = []; elements[i].elementref.push( elements[Math.pow(i, 2) % len] ); } // Then, you can access the lookupindex's document.write(document.body.lookupindex + '<br />' + Boolean(~document.body.elementref.indexOf(document.currentScript)) );
我使用WeakMap
来获取以不可变对象作为参数的函数的无忧存储caching。
记忆是一种奇特的方式:“在你计算出这个值之后,caching它,这样你就不必再计算了”。
这是一个例子:
// using immutable.js from here https://facebook.github.io/immutable-js/ const memo = new WeakMap(); let myObj = Immutable.Map({a: 5, b: 6}); function someLongComputeFunction (someImmutableObj) { // if we saved the value, then return it if (memo.has(someImmutableObj)) { console.log('used memo!'); return memo.get(someImmutableObj); } // else compute, set, and return const computedValue = someImmutableObj.get('a') + someImmutableObj.get('b'); memo.set(someImmutableObj, computedValue); console.log('computed value'); return computedValue; } someLongComputeFunction(myObj); someLongComputeFunction(myObj); someLongComputeFunction(myObj); // reassign myObj = Immutable.Map({a: 7, b: 8}); someLongComputeFunction(myObj);
<script src="ajax/libs/immutable/3.8.1/immutable.min.js"></script>
(目前正在编辑答案..)