val*_*man 374 javascript weakmap ecmascript-6
WeakMapECMAScript 6中引入的数据结构的实际用途是什么?
由于弱映射的键会创建对其相应值的强引用,因此只要其键仍处于活动状态,确保插入到弱映射中的值永远不会消失,它不能用于备注表,缓存或您通常使用弱引用的任何其他内容,具有弱值的映射等.
在我看来,这个:
weakmap.set(key, value);
Run Code Online (Sandbox Code Playgroud)
......这只是一种迂回的说法:
key.value = value;
Run Code Online (Sandbox Code Playgroud)
我错过了哪些具体的用例?
Ben*_*aum 477
WeakMaps提供了一种从外部扩展对象而不会干扰垃圾收集的方法.每当你想扩展一个对象但不能因为它被密封 - 或者从外部源 - 时,可以应用WeakMap.
WeakMap是一个映射(字典),其中键是弱的 - 也就是说,如果对键的所有引用都丢失并且没有对该值的更多引用 - 该值可以被垃圾收集.让我们先通过例子说明这一点,然后解释一下,最后以实际用途结束.
假设我正在使用一个给我一个特定对象的API:
var obj = getObjectFromLibrary();
Run Code Online (Sandbox Code Playgroud)
现在,我有一个使用该对象的方法:
function useObj(obj){
doSomethingWith(obj);
}
Run Code Online (Sandbox Code Playgroud)
我想跟踪使用某个对象调用该方法的次数,并报告它是否发生超过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);
}
Run Code Online (Sandbox Code Playgroud)
这有效,但它有内存泄漏 - 我们现在跟踪传递给函数的每个库对象,这使得库对象不会被垃圾收集.相反 - 我们可以使用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);
}
Run Code Online (Sandbox Code Playgroud)
内存泄漏消失了.
一些用例会导致内存泄漏并由WeakMaps 启用,包括:
它可以用于从外部扩展对象.让我们从Node.js的真实世界中给出一个实用的(适应的,真实的 - 来说明一点).
假设你是Node.js并且你有Promise对象 - 现在你想要跟踪所有当前被拒绝的承诺 - 但是,如果没有引用它们,你不希望它们被垃圾收集.
现在,您不希望出于显而易见的原因向本机对象添加属性 - 因此您将陷入困境.如果您继续引用承诺,则会导致内存泄漏,因为不会发生垃圾回收.如果您不保留参考文献,则无法保存有关个人承诺的其他信息.任何涉及保存承诺ID的方案本身就意味着您需要对它的引用.
WeakMaps意味着密钥很弱.没有办法枚举弱地图或获取其所有值.在弱映射中,您可以基于密钥存储数据,以及何时密钥被垃圾收集,因此值.
这意味着给定一个承诺,您可以存储关于它的状态 - 并且该对象仍然可以被垃圾收集.稍后,如果您获得对象的引用,您可以检查是否有任何与之相关的状态并进行报告.
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
});
Run Code Online (Sandbox Code Playgroud)
我们在地图中保留有关承诺的信息,并且可以知道何时处理了被拒绝的承诺.
axe*_*uch 47
一个用例可能是将它用作听众的字典,我有一个同事那样做.这非常有用,因为任何听众都直接以这种做事方式为目标.再见listener.on.
但从更抽象的角度来看,WeakMap对基本上任何东西的取消实现特别强大,你不需要一个命名空间来隔离它的成员,因为它已经被这个结构的本质所暗示.我很确定你可以通过替换笨拙的冗余对象密钥来做一些重大的内存改进(即使解构为你工作).
我现在意识到我的强调并不是解决这个问题的最佳方法,正如本杰明·格伦鲍姆所指出的那样(看看他的答案,如果它还没有超过我的答案: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);
}
}
Run Code Online (Sandbox Code Playgroud)
Mic*_*ski 16
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
Run Code Online (Sandbox Code Playgroud)
我们使用该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;
}());
})()
Run Code Online (Sandbox Code Playgroud)
Ric*_*ler 10
我WeakMap用于缓存无忧的memoization函数,这些函数将不可变对象作为参数.
记忆是一种奇特的说法,"在计算出值后,将其缓存,这样你就不必再计算它".
这是一个例子:
// 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);Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.min.js"></script>Run Code Online (Sandbox Code Playgroud)
有几点需要注意:
Yus*_*han 10
WEAKMAP:请记住,weakMap 是关于内存分配和垃圾收集的,仅与对象类型的键相关\n javascript,当您将值存储在键值对数组、映射、集合等中时...分配给所有键的内存-值对,即使您删除该键或将该键设置为 null,该内存也不会释放,请考虑将其视为强映射键强烈附加到内存,下面是示例
\nlet john = { name: "yusuf" };\n\nlet map = new Map();\nmap.set(yusuf, "xyz"); //here "yusuf" is the key and "xyz" is value\n\nyusuf= null; // overwrite the reference\n\n// the object previously referenced by yusuf is stored inside the array\n// therefore it won\'t be garbage-collected\n// we can get it using map.keys()\nRun Code Online (Sandbox Code Playgroud)\n但弱映射的情况并非如此,这里内存将被释放
\nlet john = { name: "yusuf" };\n\nlet map = new WeakMap();\nmap.set(yusuf, "...");\n\nyusuf= null; // overwrite the reference\n\n// yusuf is removed from memory!\nRun Code Online (Sandbox Code Playgroud)\n用例:您将在 javascript 中使用它,您希望以更有效的方式管理内存
\n如果我们\xe2\x80\x99正在使用一个\xe2\x80\x9c属于\xe2\x80\x9d到另一个代码(甚至可能是第三方库)的对象,并且想要存储一些与其关联的数据,那么应该仅当对象处于活动状态时才存在 \xe2\x80\x93 那么 WeakMap 正是 \xe2\x80\x99s 所需要的。
\n我们将数据放入 WeakMap,使用对象作为键,当对象被垃圾回收时,该数据也会自动消失。
\nweakMap.set(yusuf, "secret documents");\n// if yusuf dies, secret documents will be destroyed automatically\nRun Code Online (Sandbox Code Playgroud)\n我参考了这篇很棒的文章:https ://javascript.info/weakmap-weakset
\n弱映射可用于存储有关DOM元素的元数据,而不会干扰垃圾回收或使同事生气于您的代码。例如,您可以使用它们对网页中的所有元素进行数字索引。
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
// For those of you new to javascirpt, I hope the comments below help explain
// how the ternary operator (?:) works like an inline if-statement
document.write(document.body.lookupindex + '<br />' + (
(document.body.elementref.indexOf(document.currentScript) !== -1)
? // if(document.body.elementref.indexOf(document.currentScript) !== -1){
"true"
: // } else {
"false"
) // }
);Run Code Online (Sandbox Code Playgroud)
var DOMref = new WeakMap(),
__DOMref_value = Array,
__DOMref_lookupindex = 0,
__DOMref_otherelement = 1,
elements = document.getElementsByTagName('*'),
i = -1, len = elements.length, cur;
while (++i !== len) {
// Production code written this greatly makes me want to :
cur = DOMref.get(elements[i]);
if (cur === undefined)
DOMref.set(elements[i], cur = new __DOMref_value)
cur[__DOMref_lookupindex] = i;
cur[__DOMref_otherelement] = new WeakSet();
cur[__DOMref_otherelement].add( elements[Math.pow(i, 2) % len] );
}
// Then, you can access the lookupindex's
cur = DOMref.get(document.body)
document.write(cur[__DOMref_lookupindex] + '<br />' + (
cur[__DOMref_otherelement].has(document.currentScript)
? // if(cur[__DOMref_otherelement].has(document.currentScript)){
"true"
: // } else {
"false"
) // }
);Run Code Online (Sandbox Code Playgroud)
除了弱映射版本更长的事实之外,这种差异看起来可以忽略不计,但是上面显示的两段代码之间存在很大的差异。在没有弱映射的第一段代码中,这段代码存储DOM元素之间的所有引用。这样可以防止DOM元素被垃圾回收。Math.pow(i, 2) % len]似乎没有人会使用,但是再三考虑:很多生产代码中都有DOM引用,这些引用在整个文档中都会出现。现在,对于第二段代码,由于对元素的所有引用都很弱,因此在删除节点时,浏览器能够确定未使用该节点(您的代码无法访问该节点),并且因此将其从内存中删除。之所以要关注内存使用情况和内存锚点(例如未使用的元素保留在内存中的第一个代码段)之类的原因,是因为更多的内存使用率意味着更多的浏览器尝试使用GC(尝试将内存释放给避免浏览器崩溃)意味着浏览体验变慢,有时还会导致浏览器崩溃。
至于用于这些的polyfill,我会推荐我自己的库(在@github上找到)。它是一个非常轻量级的库,可以简单地对其进行填充,而无需您在其他polyfill中找到任何过于复杂的框架。
〜编码愉快!
我有这个基于简单功能的 WeakMaps 用例/示例。
我从一个UserObject开始,它的属性包括 a fullname、username、和一个被调用的方法age,gender该方法print打印其他属性的人类可读摘要。
/**
Basic User Object with common properties.
*/
function User(username, fullname, age, gender) {
this.username = username;
this.fullname = fullname;
this.age = age;
this.gender = gender;
this.print = () => console.log(`${this.fullname} is a ${age} year old ${gender}`);
}
Run Code Online (Sandbox Code Playgroud)
然后,我添加了一个 Map 调用users以保留多个用户的集合,这些用户由username.
/**
Collection of Users, keyed by username.
*/
var users = new Map();
Run Code Online (Sandbox Code Playgroud)
添加集合还需要辅助函数来添加、获取、删除用户,甚至为了完整起见打印所有用户的函数。
/**
Creates an User Object and adds it to the users Collection.
*/
var addUser = (username, fullname, age, gender) => {
let an_user = new User(username, fullname, age, gender);
users.set(username, an_user);
}
/**
Returns an User Object associated with the given username in the Collection.
*/
var getUser = (username) => {
return users.get(username);
}
/**
Deletes an User Object associated with the given username in the Collection.
*/
var deleteUser = (username) => {
users.delete(username);
}
/**
Prints summary of all the User Objects in the Collection.
*/
var printUsers = () => {
users.forEach((user) => {
user.print();
});
}
Run Code Online (Sandbox Code Playgroud)
上面所有的代码都运行在NodeJS 中,users在整个过程中只有Map 具有对用户对象的引用。没有对单个用户对象的其他引用。
在交互式 NodeJS shell 中运行此代码,作为示例,我添加了四个用户并打印它们:

现在说需要一个新功能,其中每个用户的社交媒体平台 (SMP) 链接需要与用户对象一起跟踪。
这里的关键还在于必须在对现有代码的干预最少的情况下实现此功能。
WeakMaps 可以通过以下方式实现这一点。
我为 Twitter、Facebook、LinkedIn 添加了三个单独的 WeakMap。
/*
WeakMaps for Social Media Platforms (SMPs).
Could be replaced by a single Map which can grow
dynamically based on different SMP names . . . anyway...
*/
var sm_platform_twitter = new WeakMap();
var sm_platform_facebook = new WeakMap();
var sm_platform_linkedin = new WeakMap();
Run Code Online (Sandbox Code Playgroud)
添加一个辅助函数getSMPWeakMap只是为了返回与给定 SMP 名称关联的 WeakMap。
/**
Returns the WeakMap for the given SMP.
*/
var getSMPWeakMap = (sm_platform) => {
if(sm_platform == "Twitter") {
return sm_platform_twitter;
}
else if(sm_platform == "Facebook") {
return sm_platform_facebook;
}
else if(sm_platform == "LinkedIn") {
return sm_platform_linkedin;
}
return undefined;
}
Run Code Online (Sandbox Code Playgroud)
将用户 SMP 链接添加到给定 SMP WeakMap 的函数。
/**
Adds a SMP link associated with a given User. The User must be already added to the Collection.
*/
var addUserSocialMediaLink = (username, sm_platform, sm_link) => {
let user = getUser(username);
let sm_platform_weakmap = getSMPWeakMap(sm_platform);
if(user && sm_platform_weakmap) {
sm_platform_weakmap.set(user, sm_link);
}
}
Run Code Online (Sandbox Code Playgroud)
仅打印给定 SMP 上存在的用户的功能。
/**
Prints the User's fullname and corresponding SMP link of only those Users which are on the given SMP.
*/
var printSMPUsers = (sm_platform) => {
let sm_platform_weakmap = getSMPWeakMap(sm_platform);
console.log(`Users of ${sm_platform}:`)
users.forEach((user)=>{
if(sm_platform_weakmap.has(user)) {
console.log(`\t${user.fullname} : ${sm_platform_weakmap.get(user)}`)
}
});
}
Run Code Online (Sandbox Code Playgroud)
您现在可以为用户添加 SMP 链接,还可以让每个用户在多个 SMP 上拥有一个链接。
...继续前面的示例,我向用户添加 SMP 链接,为用户 Bill 和 Sarah 添加多个链接,然后分别打印每个 SMP 的链接:

现在假设users通过调用从Map 中删除了一个用户deleteUser。这将删除对用户对象的唯一引用。这反过来也会从任何/所有 SMP WeakMaps(通过垃圾收集)清除 SMP 链接,因为没有用户对象,就无法访问其任何 SMP 链接。
...继续示例,我删除用户Bill,然后打印出与他关联的 SMP 的链接:
不需要任何额外的代码来单独删除 SMP 链接,并且在此功能之前的现有代码没有进行任何修改。
如果有任何其他方式可以在有/没有 WeakMaps 的情况下添加此功能,请随时发表评论。