【问题标题】:ECMAScript 6: what is WeakSet for?ECMAScript 6:WeakSet 有什么用?
【发布时间】:2015-08-13 21:13:26
【问题描述】:

WeakSet 应该通过弱引用来存储元素。也就是说,如果一个对象没有被其他任何东西引用,则应该从 WeakSet 中清除它。

我已经写了以下测试:

var weakset = new WeakSet(),
    numbers = [1, 2, 3];

weakset.add(numbers);
weakset.add({name: "Charlie"});

console.log(weakset);

numbers = undefined;

console.log(weakset);

即使我的 [1, 2, 3] 数组没有被任何东西引用,它也不会从 WeakSet 中删除。控制台打印:

WeakSet {[1, 2, 3], Object {name: "Charlie"}}
WeakSet {[1, 2, 3], Object {name: "Charlie"}}

这是为什么呢?

另外,我还有一个问题。像这样直接向 WeakSets 添加对象有什么意义:

weakset.add({name: "Charlie"});

是 Traceur 的故障还是我遗漏了什么?

最后,如果我们甚至无法遍历它也无法获得当前大小,那么 WeakSet 的实际用途是什么?

【问题讨论】:

  • @RahulTripathi 是的,我看过这个文档,但它没有回答我的问题。
  • @Teemu 是的,它不会删除变量,但会删除引用。在这种情况下,它是最后一个引用,这意味着数组对象变成了不可访问的垃圾。 :)
  • 任何解决方案中没有提到的一件事是关于您对控制台的问题。我刚刚处理了这个问题,发现控制台保留了对 WeakSet() 的引用,因此它会一直保留在那里,直到您关闭并重新打开控制台。永远在 WTF 模式下度过,直到我这样做并繁荣 - 所有的 WeakSet 看起来都应该如此!我觉得该属性可以为垃圾收集器提供一些非常好的调试,并确认正在按预期删除内容。

标签: javascript ecmascript-6 traceur


【解决方案1】:

让我回答第一部分,并尽量避免进一步混淆你。

取消引用对象的垃圾回收不可观察!这将是一个悖论,因为您需要一个对象引用来检查它是否存在于地图中。但不要相信我,相信凯尔辛普森: https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/es6%20%26%20beyond/ch5.md#weakmaps

我在这里看到的很多解释的问题是,他们将一个变量重新引用到另一个对象,或者为其分配一个原始值,然后检查 WeakMap 是否包含该对象或值作为键。当然不是!它从来没有将该对象/值作为键!

所以这个难题的最后一块:为什么在控制台中检查 WeakMap 仍然会显示那里的所有这些对象,即使您已经删除了对这些对象的所有引用?因为控制台本身保持对这些对象的持久引用,以便能够列出 WeakMap 中的所有键,因为这是 WeakMap 本身无法做到的。

【讨论】:

  • 我的问题已经 5 岁了,我不再困惑 :) 参考“你不懂 JS”的好主意!
【解决方案2】:

这是一个非常难的问题。老实说,我对 JavaScript so I asked in esdiscuss 的上下文一无所知,并从 Domenic 得到了令人信服的答案。

WeakSets 对于安全验证 的原因很有用。如果你希望能够隔离一段 JavaScript。它们允许您标记一个对象以表明它属于一组特殊的对象。

假设我有一堂课ApiRequest

class ApiRequest {
  constructor() {
    // bring object to a consistent state, use platform code you have no direct access to
  }

  makeRequest() {
    // do work 
  }
}

现在,我正在编写一个 JavaScript 平台 - 我的平台允许您运行 JavaScript 来进行调用 - 要进行这些调用,您需要 ApiRequest - 我只希望您使用我提供的对象进行 ApiRequests所以你不能绕过我的任何限制。

然而,目前没有什么能阻止你做:

ApiRequest.prototype.makeRequest.call(null, args); // make request as function
Object.create(ApiRequest.prototype).makeRequest(); // no initialization
function Foo(){}; Foo.prototype = ApiRequest.prototype; new Foo().makeRequest(); // no super

等等,请注意,您不能保留ApiRequest 对象的普通列表或数组,因为这会阻止它们被垃圾收集。除了闭包之外,任何事情都可以通过Object.getOwnPropertyNamesObject.getOwnSymbols 等公共方法实现。所以你和我一起做:

const requests = new WeakSet();
class ApiRequest {
  constructor() {
    requests.add(this);
  }

  makeRequest() {
    if(!request.has(this)) throw new Error("Invalid access");
    // do work
  }
}

现在,无论我做什么 - 我都必须持有一个有效的 ApiRequest 对象才能在其上调用 makeRequest 方法。如果没有 WeakMap/WeakSet,这是不可能的。

简而言之 - WeakMaps 对于用 JavaScript 编写平台很有用。通常这种验证是在 C++ 端完成的,但添加这些功能将允许在 JavaScript 中移动和制作东西。

(当然,WeakSet 所做的所有事情都可以通过 WeakMap 将值映射到 true,但对于任何 map/set 构造都是如此)

(就像 Bergi 的回答所暗示的那样,永远没有理由将对象文字直接添加到 WeakMapWeakSet

【讨论】:

  • 你不能用强集合来做大部分事情吗?您必须在某些时候删除它们或泄漏请求,但这可以在 makeRequest 方法中轻松完成,因为请求通常被视为一次性使用。
  • @ssube 如果可以拨打多个makeRequest 怎么办?如果ApiRequest 实例通过但有时根本没有使用(0 次调用)怎么办?它必须是弱的原因是不将请求的生命周期绑定到集合。
  • @BenjaminGruenbaum, 1) 如果实用性仅限于编写此类平台的便利性,那么说实话,它根本不应该被添加到主要语言中。 2) 可以使用if(static_symbol !== arg1) throw new Error("Invalid access"); 编写此类平台。 stackoverflow.com/questions/29413222/… 为此使用弱集几乎是一种 hack。
  • @Pacerier 2) 不,它不能,而且它肯定不是一个 hack - 如果你不知道我在 esdiscuss 中得到的回复是那些在语言中添加东西的人:D
【解决方案3】:

当您需要保留任意的事物集合但不希望它们在集合中的存在阻止这些事物在内存紧张时被垃圾收集时,“弱”集合或映射很有用。 (如果垃圾回收确实发生了,“回收”的对象会默默地从回收中消失,所以你可以知道它们是否已经消失了。)

它们非常出色,例如,用作后备缓存:“我最近是否已经检索过这条记录?” 每次检索某些内容时,将其放入地图中,知道JavaScript 垃圾收集器 将负责为您“修剪列表”,并且它会自动执行此操作以响应当前的内存条件(您无法合理预期)。

唯一的缺点是这些类型不是“可枚举的”。您不能遍历条目列表——可能是因为这可能会“触及”这些条目并因此破坏目的。但是,这是一个很小的代价(如果需要,您可以“围绕它编写代码”)。

【讨论】:

    【解决方案4】:

    由于垃圾收集尚未发生,您的控制台可能错误地显示了内容。因此,由于该对象没有被垃圾回收,它会显示该对象仍处于弱集状态。

    如果您真的想查看弱集是否仍然具有对某个对象的引用,请使用WeakSet.prototype.has() 方法。该方法,顾名思义,返回一个boolean,表示该对象是否仍然存在于弱集中。

    示例:

    var weakset = new WeakSet(),
        numbers = [1, 2, 3];
    
    weakset.add(numbers);
    weakset.add({name: "Charlie"});
    
    console.log(weakset.has(numbers));
    
    numbers = undefined;
    
    console.log(weakset.has(numbers));

    【讨论】:

      【解决方案5】:

      WeakSet 是 WeakMap 的简化版,您的值始终为布尔值 true。它允许您标记 JavaScript 对象,以便只对它们执行一次操作或维护它们在特定进程中的状态。理论上,由于它不需要保存值,因此它应该使用更少的内存并且比 WeakMap 执行速度稍快。

      var [touch, untouch] = (() => {
          var seen = new WeakSet();
          return [
              value => seen.has(value)) || (seen.add(value), !1),
              value => !seen.has(value) || (seen.delete(value), !1)
          ];
      })();
      
      function convert(object) {
          if(touch(object)) return;
          extend(object, yunoprototype); // Made up.
      };
      
      function unconvert(object) {
          if(untouch(object)) return;
          del_props(object, Object.keys(yunoprototype)); // Never do this IRL.
      };
      

      【讨论】:

      • 您的示例代码没有解释如何使用弱集。
      • 我一定很着急。描述更重要。代码是 stubble/prototype 用于改变对象一次且仅一次(例如扩展它,运行时多态性)。
      【解决方案6】:

      它没有从 WeakSet 中移除。这是为什么呢?

      很可能是因为垃圾收集器尚未运行。但是,您说您正在使用 Traceur,因此可能是它们没有得到适当的支持。我想知道console 是如何显示WeakSet 的内容的。

      直接将对象添加到 WeakSets 有什么意义?

      WeakSets 添加对象字面量绝对没有意义。

      如果我们甚至无法遍历它也无法获取当前大小,那么 WeakSet 的实际用途是什么?

      你所能得到的只是一点信息:对象(或一般来说,值)是否包含在集合中?

      这在您想要“标记”对象而不实际改变它们(在它们上设置属性)的情况下很有用。许多算法都包含某种“如果已经看到x”条件(JSON.stringify 循环检测可能是一个很好的例子),当您使用用户提供的值时,使用Set/@987654327 @ 将是可取的。 WeakSet 这里的优点是它的内容可以在你的算法仍在运行时被垃圾收集,所以当你处理大量惰性数据时它有助于减少内存消耗(甚至防止泄漏)(可能甚至异步)产生。

      【讨论】:

      • 感谢您使用“标记”这个词——我现在明白了整个要点。那么迭代和.size 在 WeakSets 中不可用,因为它们不可靠吗?垃圾收集器随时工作,因此这些信息没有任何用处。但是.has() 是不同的故事,因为如果我们有任何引用将它传递给.has() 方法,垃圾收集器肯定没有清理它。我的理解合理吗? :)
      • @RoboRobok:正是这样 :-) 可以使 WeakSet 实现可迭代,但如果以错误的方式使用,可能会导致非确定性算法(取决于 GC 行为),并且因此 ES 委员会决定不提供这些内容。
      • @RoboRobok 我认为另一个答案很好地显示了 WeakSet 的主要用途。我认为它不会经常在应用程序代码中使用,但经常在 jQuery 和 Angular 等库中使用。在具有大量 AJAX 数据加载的 SPA(单页应用程序)中如何处理此类事情可能效率低下,其中内存管理在很长一段时间内变得棘手。当你有一个标准的面向页面的网站时,内存无论如何都会被丢弃,所以内存管理就不是问题了。
      • @pid 谢谢你的解释。还有一件事:如果直接向 WeakSet 添加任何内容没有意义,如果我们尝试这样做,它(理论上)会抛出错误吗?或者在 JavaScript 中可能无法确定某些东西是否通过变量传递?
      • @RoboRobok:对于 linter 或依赖于实现的特性(依赖于内部表示的东西)来说,这更多的是东西。在纯 JavaScript 中,所有对象都是引用值,当将对象传递给函数时,从字面量动态构建的对象与存储在变量中的对象之间没有明显的区别。
      【解决方案7】:

      根据定义,WeakSet 只有三个关键功能

      • 将对象弱链接到集合中
      • 从集中删除对象的链接
      • 检查对象是否已链接到集合

      听起来比较熟悉?

      在某些应用程序中,开发人员可能需要实现一种快速的方法来遍历被大量冗余污染的一系列数据但您只想选择以前没有处理过的数据(唯一) 。 WeakSet 可以帮助你。请参阅下面的示例:

      var processedBag = new WeakSet();
      var nextObject = getNext();
      while (nextObject !== null){
          // Check if already processed this similar object?
          if (!processedBag.has(nextObject)){
              // If not, process it and memorize 
              process(nextObject);
              processedBag.add(nextObject);
          }
          nextObject = getNext();
      }
      

      上述应用的最佳数据结构之一是布隆过滤器,它非常适合海量数据。但是,您也可以为此目的应用 WeakSet

      【讨论】:

      • 请注意,processedBag.has(nextObject) 在您插入对象后将始终返回 true,因为通过引用它,您将阻止 GC 收集它。 换句话说,如果您在 var 超出范围时不需要弱集的自动删除功能,那么您不妨使用普通集
      • nextObject = getNext(); 行不会给出另一个可能不在processedBag 中的对象吗?
      猜你喜欢
      • 2014-03-02
      • 2011-10-10
      • 1970-01-01
      • 2011-05-03
      • 2014-07-23
      • 2015-03-29
      • 1970-01-01
      • 2018-09-11
      相关资源
      最近更新 更多