ECMAScript 6中引入的WeakMap数据结构的实际用途是什么?

由于弱映射的键创建了对其对应值的强引用,确保插入到弱映射中的值只要其键仍然存在就不会消失,因此它不能用于备忘录表、缓存或其他通常使用弱引用、具有弱值的映射等的任何东西。

在我看来:

weakmap.set(key, value);

...只是拐弯抹角地说

key.value = value;

我遗漏了哪些具体的用例?


当前回答

我使用WeakMap缓存以不可变对象作为参数的函数。

记忆是一种花哨的说法,意思是“在你计算出值之后,缓存它,这样你就不必再次计算它”。

这里有一个例子:

// 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="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.min.js"></script>

有几件事需要注意:

当你修改Immutable.js对象时返回新对象(带有新指针),因此在WeakMap中使用它们作为键可以保证相同的计算值。 WeakMap非常适合memo,因为一旦对象(用作键)被垃圾收集,WeakMap上的计算值也会被垃圾收集。

其他回答

这个答案似乎是有偏见的,在现实世界中是不可用的。请原原本本地阅读,不要把它看作是一种实际的选择,而只是一种实验

用例可以是将它用作监听器的字典,我有一个同事就是这样做的。这是非常有用的,因为任何听众都可以直接以这种方式做事。再见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);
    }
}

我认为这是非常有用的检查连接收入在应用程序套接字。 另一种情况,'Weak Collection'是有用的:https://javascript.info/task/recipients-read

WEAKMAP:请记住,WEAKMAP是关于内存分配和垃圾收集的,并且只与对象类型的键相关 在javascript中,当你存储值在键值对数组,映射,设置等…一个分配给所有键值对的内存,即使你删除或将该键设置为null,这个内存也不会被释放,将此视为强映射键强附加到内存下面是一个例子

let john = { name: "yusuf" };

let map = new Map();
map.set(yusuf, "xyz"); //here "yusuf" is the key and "xyz" is value

yusuf= null; // overwrite the reference

// the object previously referenced by yusuf is stored inside the array
// therefore it won't be garbage-collected
// we can get it using map.keys()

但这不是weakMap的情况,在这里内存将是空闲的

let john = { name: "yusuf" };

let map = new WeakMap();
map.set(yusuf, "...");

yusuf= null; // overwrite the reference

// yusuf is removed from memory!

用例:你会在javascript中使用它,你想以更有效的方式管理内存

如果我们正在处理一个“属于”另一个代码的对象,甚至可能是一个第三方库,并且想要存储一些与之相关的数据,这些数据应该只在对象存活时存在——那么WeakMap正是所需要的。

我们将数据放到WeakMap中,使用对象作为键,当对象被垃圾收集时,该数据也将自动消失。

weakMap.set(yusuf, "secret documents");
// if yusuf dies, secret documents will be destroyed automatically

我参考了这篇很棒的文章:https://javascript.info/weakmap-weakset

我有一个简单的基于弱地图功能的用例/示例。

管理用户集合

我从一个User对象开始,它的属性包括全名、用户名、年龄、性别和一个名为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}`);
}

然后,我添加了一个名为users的Map,以保存多个用户的集合,这些用户由用户名输入。

/**
Collection of Users, keyed by username.
*/
var users = new Map();

Collection的添加还需要辅助函数来添加、获取、删除User,甚至为了完整起见,还需要一个函数来打印所有用户。

/**
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();
    });
}

在运行上述所有代码时,比如NodeJS,在整个进程中只有用户映射具有对用户对象的引用。没有其他对单个用户对象的引用。

在交互式NodeJS shell中运行这段代码,就像一个例子,我添加了四个用户并打印他们:

在不修改现有代码的情况下向用户添加更多信息

现在,假设需要一个新功能,其中每个用户的社交媒体平台(SMP)链接需要与用户对象一起跟踪。

这里的关键还在于,该特性的实现必须对现有代码进行最少的干预。

这可以通过以下方式使用weakmap实现。

我为Twitter, Facebook, LinkedIn添加了三个单独的弱地图。

/*
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();

添加帮助函数getSMPWeakMap只是为了返回与给定SMP名称相关联的弱映射。

/**
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;
}

将用户SMP链接添加到给定SMP弱映射的函数。

/**
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);
    }
}

只打印在给定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)}`)
        }
    });
}

您现在可以为用户添加SMP链接,还可以让每个用户在多个SMP上拥有一个链接。

…继续前面的示例,我为用户添加SMP链接,为用户Bill和Sarah添加多个链接,然后分别打印每个SMP的链接:

现在,假设通过调用deleteUser从用户映射中删除了一个User。这将删除对User对象的唯一引用。这反过来也将从任何/所有SMP弱映射中清除SMP链接(通过垃圾收集),因为没有User对象就没有办法访问它的任何SMP链接。

...继续示例,我删除用户Bill,然后打印出与他相关的smp的链接:

不需要任何额外的代码来单独删除SMP链接,并且在此特性之前的现有代码没有任何修改。

如果有任何其他方式添加这个功能,或没有弱地图,请随时评论。

我使用WeakMap缓存以不可变对象作为参数的函数。

记忆是一种花哨的说法,意思是“在你计算出值之后,缓存它,这样你就不必再次计算它”。

这里有一个例子:

// 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="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.min.js"></script>

有几件事需要注意:

当你修改Immutable.js对象时返回新对象(带有新指针),因此在WeakMap中使用它们作为键可以保证相同的计算值。 WeakMap非常适合memo,因为一旦对象(用作键)被垃圾收集,WeakMap上的计算值也会被垃圾收集。