如何实现 JS 真正意义上的弱引用?

阅读数:1610 2019 年 7 月 25 日 18:41

如何实现JS真正意义上的弱引用?

本文将详细解释 JavaScript 中对象的引用是强引用,WeakMap 和 WeakSet 可以提供部分的弱引用功能,若想在 JavaScript 中实现真正的弱引用,可以通过配合使用 WeakRef 和终结器(Finalizer)来实现。

一般来说,在 JavaScript 中,对象的引用是强保留的,这意味着只要持有对象的引用,它就不会被垃圾回收。

复制代码
const ref = { x: 42, y: 51 };
// 只要我们访问 ref 对象(或者任何其他引用指向该对象),这个对象就不会被垃圾回收

目前,在 Javascript 中,WeakMap 和 WeakSet 是弱引用对象的唯一方法:将对象作为键添加到 WeakMap 或 WeakSet 中,是不会阻止它被垃圾回收的。

复制代码
const wm = new WeakMap();
{
const ref = {};
const metaData = 'foo';
wm.set(ref, metaData);
wm.get(ref);
// 返回 metaData
}
// 在这个块范围内,我们已经没有对 ref 对象的引用。
// 因此,虽然它是 wm 中的键,我们仍然可以访问,但是它能够被垃圾回收。
const ws = new WeakSet();
ws.add(ref);
ws.has(ref);
// 返回 true

你可以认为 WeakMap.prototype.set(ref, metaData) 是向对象 ref 中添加值为 metaData 的属性:只要你持有对象的引用,就可以获取元数据。一旦不再持有对象的引用,即使你仍持有添加了该对象的 WeakMap 的引用对象,也会被垃圾回收。类似地,可以将 WeakSet 视为 WeakMap 中所有值都是布尔值的一个特例。

JavaScript 的 WeakMap 并不是真正意义上的弱引用:实际上,只要键仍然存活,它就强引用其内容。WeakMap 仅在键被垃圾回收之后,才弱引用它的内容。这种关系更准确地称为 ephemeron

WeakRef 是一个更高级的 API,它提供了真正的弱引用,并在对象的生命周期中插入了一个窗口。让我们一起来看个例子。

假设有一个 getImage 函数,它接受一个 name 入参,并执行一些昂贵的操作来生成另一个对象,比如生成二进制图像数据:

复制代码
function getImage(name) {
const image = performExpensiveOperation(name);
return image;
}

为了提高性能,我们将图像保存在缓存中。现在,我们不必再为相同的入参执行昂贵的操作了!

复制代码
const cache = new Map();
function getImageCached(name) {
if (cache.has(name)) return cache.get(name);
const image = performExpensiveOperation(name);
cache.set(name, image);
return image;
}

但是,这里存在一个问题。Map 会强保留它的键和值,因此,图像名称和数据永远不会被垃圾回收。这会逐步增加内存占用,最终导致内存泄漏

WeakRef 通过创建图像对象的弱引用并将弱引用保存在缓存中(而不是保存图像对象本身)来解决内存泄漏问题。这样,垃圾回收器就可以清除没有强引用的图像对象了。

复制代码
const cache = new Map();
function getImageCached(name) {
const ref = cache.get(name);
if (ref !== undefined) {
const deref = ref.deref();
if (deref !== undefined) return deref;
}
const image = performExpensiveOperation(name);
const wr = new WeakRef(image);
cache.set(name, wr);
return image;
}

但这里仍然存在一个问题:Map 仍然永远保留 name 的字符串,因为这些字符串是缓存中的键。理想情况下,这些字符串也要被删除。 WeakRef 提案中也提供了一个解决方案!通过新的 FinalizationGroup API,我们可以注册一个回调,以便在垃圾回收器回收已注册的对象时运行。这种回调称为终结器(Finalizers)

注意:在垃圾回收器回收图像对象之后,终结回调不会立即运行。它可能在将来某个时候运行,甚至根本不运行——规范并不保证它一定运行!编写代码时,请注意这一点。

在此,我们注册一个回调,以便在图像对象被垃圾回收时从缓存中删除键:

复制代码
const cache = new Map();
const finalizationGroup = new FinalizationGroup((iterator) => {
for (const name of iterator) {
const ref = cache.get(name);
if (ref !== undefined && ref.deref() === undefined) {
cache.delete(name);
}
}
});

注意:ref !== undefined && ref.deref() === undefined 是必需的,因为在旧WeakRef进入终结回调队列和实际运行终结回调之间,可能已经添加了同一个name的新WeakRef

我们的最终实现如下:

复制代码
const cache = new Map();
const finalizationGroup = new FinalizationGroup((iterator) => {
for (const name of iterator) {
const ref = cache.get(name);
if (ref !== undefined && ref.deref() === undefined) {
cache.delete(name);
}
}
});
function getImageCached(name) {
const ref = cache.get(name); // 1
if (ref !== undefined) { // 2
const deref = ref.deref();
if (deref !== undefined) return deref;
}
const image = performExpensiveOperation(name); // 3
const wr = new WeakRef(image); // 4
cache.set(name, wr); // 5
finalizationGroup.register(image, name); // 6
return image; // 7
}

给定一个图像名称(入参),我们在缓存中查找其对应的弱引用(1)。如果弱引用仍然指向某个对象(2),那么我们就返回缓存的图像数据。如果该图像名称对应的弱引入没有在缓存中,或者缓存的图像数据被垃圾回收了,那么我们计算图像数据(3),创建一个新的弱引用(4),将图像名称和弱引用保存到缓存中(5),注册一个终结器,一旦图像数据被垃圾回收,该终结器就删除缓存中图像名称(6),最后返回图像(7)。

过犹不及

听到这些新功能之后,可能会忍不住想要尝试 WeakRef 的所有这些功能(All The Things™)。不过,这可能不是个好主意。对于 WeakRef 和终结器来说,有很多明显不好的使用案例。

一般来说,不要编写依靠垃圾回收器清理 WeakRef 或者在任何可预测的时间调用终结器的代码——这是不可能的

例如,不要在终结器的代码块中放置重要的逻辑。因为,无法预测什么时候,甚至不确定是否会调用给定的终结器。最好将 WeakRef 和终结器视为渐进式增强:如果自定义终结器代码运行了,那最好;但是,在它没有运行的情况下,程序应该仍然可以正常运行。

WeakRef 和终结器可以帮助我们节省内存,并且在被当一种作渐进式增强的手段少量使用时,效果最佳。由于它们是高级用户特性,我们期望尽量只在框架或库中使用它们。

WeakRef 支持

如何实现JS真正意义上的弱引用?

关于此功能的支持列表: https://v8.dev/features/support

英文原文: https://v8.dev/features/weak-references

评论

发布
用户头像
一分钟大发快三技巧稳赢方式方法!金牌导师带你快速回血
老师359213571

如果你是刚刚玩,我来教教你,如果你已经玩很久了,却不稳,我来拉拉你,如果你已经遍体鳞伤,我来帮帮你。

我不能保证你一夜致富但希望能细水长流,汇聚江海,先要平稳的心态,不要一盘的失误影响你心情。

自己有规划性和目标性。做到这两点,过来找我我来帮你。

世界没有不努力就能盈利的。如果这些你觉得没时间精力去观察。那我只能劝你去跟计划了。最重要的还是你要学会耐心观察走势。

每种方法。只要你耐心观察。那种方法都可以盈利。以下是我最简单的跟号走势。

相信能进来看这贴的人都应该清楚快3是什么,又或者已经有人被它搞得伤痕累累。是不是你赢了一天,或一个星期的,

到最后都会在一个小时内就把你以前所赢的通通吐出来? 是不是你开始玩这个的时候你觉得钱来的实在太容易了?

你会发现你每天这样赢下去一个月后你就可以买辆宝马了? 但我告诉你,任何以堵钱的心态来玩的,你到最后都会输得一败涂地,

所以我们不要把它当成一种堵搏,把它当成一种投资的心态。又或者反它当成股票来操作。

以下我就说说几点;

1、不单要学会赢还要懂得怎样去止损,定下个止损目标是为了帮助自己在失手的时候有个损失限度,不受到翻本情绪的影响导致自己更加的错误下去。

这里就跟炒股一样,有止损就一定要有止赢,不要以为你赢那天你会一直赢下去,我告诉你,玩彩十个有九个都是一开始赢后面爆了帐户的。

彩是每天都会有,不要急着打回来,今天输了,明天还有机会。今天挂了。改日再战!

2、稳赚方法技巧是有的,但我在这里说的稳赚是最少以一个星期为单位,短期暴利模式网上一搜一大堆,但都拼不过变态期,

一把回到解放前很多人都经历过,快三方式很多接近稳赚,我告诉自己是稳赚,但我不敢这么跟大家说,因为没有什么事情是绝对的,何况堵搏。

为什么我说很多方法都可行呢?因为我不是叫你从早上九点到晚上12点都玩,那什么方法都是会回到解放前的。抓住重要,几把就可以收工了,

用得着这么麻烦吗?而且庄家最怕你什么?不怕你赢,就怕你赢了不玩。

3、止损与止赢的比例。我个人建设止损与止赢的比例定在1:1.。就是说比如你帐号是1W的,你今天的目标是赢1千,那你一定要做到赢1千就收,

同样的到你输1千的时候你也要收。不要跟我说拿1W只赢1千很小,不合理。

我相信这么多人玩彩的没有多少人能做到平均每天10%利润,我估计99%的人都做不到,其实不是他们的技术做不到,是他们的心态做不到。
展开全部
2019 年 07 月 25 日 21:27
回复
没有更多了