3 回答
TA贡献1824条经验 获得超6个赞
从根本上
WeakMaps提供了一种从外部扩展对象而不干扰垃圾回收的方法。每当您想扩展对象但由于密封而不能扩展对象时(或者从外部源扩展)时,都可以应用WeakMap。
WeakMap是键弱的地图(词典),也就是说,如果丢失了对键的所有引用,并且不再有对该值的引用,则可以对值进行垃圾回收。让我们首先通过示例展示它,然后进行一些解释,最后完成实际使用。
假设我使用的API给了我一个特定的对象:
var obj = getObjectFromLibrary();
现在,我有一个使用该对象的方法:
function useObj(obj){
doSomethingWith(obj);
}
我想跟踪用某个对象调用该方法的次数,并报告该方法是否发生了N次以上。天真的会想到使用Map:
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包括:
保留有关特定对象的私人数据,并且仅授予参考地图的人员访问。私有符号提案中出现了一种更临时的方法,但是距离现在还有很长时间。
保留有关库对象的数据,而无需更改它们或引起开销。
保留有关少量对象的数据(其中存在许多类型的对象)不会引起JS引擎用于相同类型的对象的隐藏类问题。
在浏览器中保留有关主机对象(如DOM节点)的数据。
从外部向对象添加功能(如另一个答案中的事件发射器示例)。
让我们看一下真正的用途
它可以用于从外部扩展对象。让我们从Node.js的真实世界中给出一个实用的(经过改编的,真实的)实例。
比方说,你的Node.js,你有Promise对象-现在你想跟踪所有当前被拒绝承诺-然而,你不希望让他们被的情况下,垃圾收集没有引用存在于他们。
现在,你不希望将属性添加到显而易见的原因,本地对象-这样你就完蛋了。如果保留对Promise的引用,则将导致内存泄漏,因为不会发生垃圾回收。如果您不保留引用,那么您将无法保存有关单个承诺的其他信息。任何涉及保存承诺ID的方案本质上都意味着您需要对其进行引用。
输入弱地图
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
});
我们将有关诺言的信息保存在地图中,并且可以知道何时处理了被拒绝的诺言。
TA贡献1802条经验 获得超10个赞
在现实世界中,这个答案似乎是有偏见且无法使用的。请按原样阅读,不要将其视为除实验以外的其他选择
一个用例可能是将其用作侦听器的字典,我有一个这样做的同事。这非常有用,因为任何听众都可以通过这种方式直接成为目标。再见listener.on。
但是从更抽象的角度来看,WeakMap它对于取消对基本上所有内容的访问的实现特别强大,您不需要名称空间来隔离其成员,因为此结构的性质已经暗示了该名称空间。我很确定您可以通过替换笨拙的冗余对象键来进行一些重大的内存改进(即使解构可以为您工作)。
在阅读下一步之前
我现在确实意识到我的强调并不是解决问题的最佳方法,正如本杰明·格伦鲍姆(Benjamin Gruenbaum)所指出的(请查看他的回答,如果还没有超过我的:p),那么这个问题就无法通过常规解决Map,因为它本来会泄漏的,因此它的主要优点WeakMap是,鉴于它们没有保留引用,它不会干扰垃圾回收。
这是我同事的实际代码(感谢他的分享)
这里的完整源代码是关于我在上面讨论的监听器管理的(您也可以查看规范)
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);
}
}
TA贡献2021条经验 获得超8个赞
WeakMap 适用于封装和信息隐藏
WeakMap仅适用于ES6及更高版本。A WeakMap是键和值对的集合,其中键必须是对象。在以下示例中,我们构建了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()方法定义了一个对象和另一个项目(在我们的例子中为字符串)之间的关联。我们使用了该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;
}());
})()
添加回答
举报