ES6 Set、WeakSet、Map 和 WeakMap

6
已经有一些关于Map和WeakMap的问题了,例如:ES6 Map和WeakMap有什么区别?但是我想问,在哪种情况下应该使用这些数据结构?或者在偏好其中一个而不是另一个时应该考虑什么?
以下是数据结构的示例:https://github.com/lukehoban/es6features
// Sets
var s = new Set();
s.add("hello").add("goodbye").add("hello");
s.size === 2;
s.has("hello") === true;

// Maps
var m = new Map();
m.set("hello", 42);
m.set(s, 34);
m.get(s) == 34;

// Weak Maps
var wm = new WeakMap();
wm.set(s, { extra: 42 });
wm.size === undefined

// Weak Sets
var ws = new WeakSet();
ws.add({ data: 42 });
// Because the added object has no other references, it will not be held in the set

奖励。 以上哪种数据结构会产生与执行以下操作相同/类似的结果:let hash = object.create(null); hash[index] = something;

7
好的,我会尽力为您提供准确且易于理解的翻译。请问您需要翻译什么内容? - T.J. Crowder
1
关于Map的相关问题,特别是IIFE示例的区别,请参考以下链接:Relevant question - CodingIntrigue
...还有ECMAScript 6:WeakSet是用来做什么的?。我真的倾向于将其关闭为重复内容。 - Bergi
这不仅仅是关于 WeakSet,而是在偏爱其中一种时,它不会被重复... @Bergi - ncubica
@ncubica:你肯定知道何时使用map而不是set?至于弱化它的用例,这些已经在现有的帖子中得到了明确的回答。只是你在一个帖子中问了两个问题(一个关于maps,另一个关于sets),再加上那个额外的东西,所以很难找到一个完全相同的帖子。 - Bergi
1个回答

15

这在规范的§23.3中有说明:

如果用作 WeakMap 键值对的键只能通过跟随从该 WeakMap 开始的引用链到达,那么该键值对将无法访问,并将被自动从 WeakMap 中删除。

因此,如果弱映射中的条目的键没有被任何其他东西引用,则它们将在某个时候被垃圾回收。

相比之下,Map 对其键持有强引用,如果 map 是唯一引用键的对象,则防止它们被垃圾回收。

MDN解释如下

WeakMap 的键是弱引用。这意味着,如果没有其他强引用指向键,则整个条目将由垃圾回收器从 WeakMap 中删除。

WeakSet 同样如此。

在哪种情况下应该使用这些数据结构?

任何情况下,如果你不希望有一个映射/集合使用的键防止该键被垃圾回收。以下是一些例子:

  1. 拥有特定于实例的信息,这些信息对于该实例是真正私有的,如下所示:(注意:此示例来自2015年,早在私有字段成为选项之前。在2021年这里,我将使用私有字段。)

let Thing = (() => {
    var privateData = new WeakMap();

    class Thing {
        constructor() {
            privateData[this] = {
                foo: "some value"
            };
        }

        doSomething() {
            console.log(privateData[this].foo);
        }
    }

    return Thing;
})();

无法从该作用域函数外部访问privateData中的数据。该数据由实例本身进行键控。如果没有使用WeakMap,那么您不会这样做,因为这将是一种内存泄漏,您的Thing实例将永远不会清除。但是WeakMap仅保留引用,因此,如果使用Thing实例的代码完成并释放了对实例的引用,则WeakMap不会阻止实例被垃圾回收;相反,以实例为键的条目将从映射中删除。

  • 持有你无法控制的对象的信息。假设您从某个API获取了一个对象,并且需要记住一些有关该对象的其他信息。如果该对象未封装,则可以向对象本身添加属性,但是向在您控制之外的对象添加属性只会带来麻烦。相反,您可以使用以该对象为键的WeakMap存储额外的信息。

  • WeakSet的一个用例是跟踪或品牌:假设在“使用”对象之前,您需要知道该对象是否曾经过去被“使用”,但不存储它作为对象的标志(可能因为如果它是对象上的标志,其他代码可以看到它[虽然您可以使用私有字段防止这种情况];或者因为它不是你的对象[所以私有字段无法帮助]). 例如,这可能是某种单次使用访问令牌。 WeakSet 是一种简单的方法,可以在不强制对象留在内存中的情况下执行此操作。

  • 哪个数据结构将产生与执行以下操作相同/类似的结果:let hash = Object.create(null); hash[index] = something;

    最接近的是Map,因为字符串index(属性名称)将由对象中的强引用持有(如果没有其他引用它,则该属性及其相关属性不会被回收)。


    6
    我这周一直在学习ES6/TypeScript,这已经是我第30次遇到并从你的回答中受益了,现在我已经期望接受的答案是你的。感谢你花费了这么多时间和精力来帮助这个社区和其他开发人员。 - Malekai
    2
    @LogicalBranch - 哇,谢谢!我真的很感激你抽出时间来说这些话。 :-) - T.J. Crowder

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接