深入浅出 JavaScript 弱引用

简介: 深入浅出 JavaScript 弱引用

深入浅出 JavaScript 弱引用

内存和性能管理是软件开发的重要方面,也是每个软件开发人员都应该注意的方面。虽然弱引用很有用,但在 JavaScript 中并不经常使用。在 ES6 版本中,JavaScript 引入了 WeakSetWeakMap

1. 弱引用

与强引用不同,弱引用并不阻止被引用的对象被垃圾收集器回收或收集,即使它是内存中对对象的唯一引用。

在讨论强引用、WeakSetSetWeakMapMap 之前,让我们用下面的代码片段来演示弱引用:

// 创建 WeakMap 对象的实例
let human = new WeakMap();
// 创建一个对象,并将其赋值给名为 man 的变量
let man = { name: "xiaan" };
// 调用 human 的 set 方法,并传递两个参数(键和值)给它
human.set(man, "done")
console.log(human)

以上代码的输出如下:

WeakMap {{…} => 'done'}
man = null;
console.log(human)

当我们将 man 变量重新赋值为 null 时,内存中对原始对象的唯一引用是弱引用,它来自我们前面创建的 WeakMap。当 JavaScript 引擎运行垃圾收集过程时,man 对象将从内存和我们分配给它的 WeakMap 中删除。这是因为它是一个弱引用,并且它不阻止垃圾收集。接下来我们谈谈强引用。

2. 强引用

JavaScript 中的强引用是防止对象被垃圾回收的引用。它将对象保存在内存中。

下面的代码片段说明了强引用的概念:

let man = {name: "xiaan"};
let human = [man];
man =  null;
console.log(human);

以上代码的结果如下:

// 长度为 1 的对象数组
[{…}]

由于 human 数组和对象之间存在强引用。对象被保留在内存中,可以通过以下代码访问:

console.log(human[0])

这里要注意的重要一点是,弱引用不会阻止对象被垃圾回收,而强引用却会阻止对象被垃圾回收。

3. JavaScript 的垃圾收集

与所有编程语言一样,在编写 JavaScript 时,内存管理是需要考虑的关键因素。与 C 语言不同,JavaScript 是一种高级编程语言,它在创建对象时自动分配内存,在不再需要对象时自动清除内存。当不再使用对象时清除内存的过程称为垃圾收集。在谈论 JavaScript 中的垃圾收集时,几乎不可能不触及「可达性」的概念。

3.1 可达性

在特定作用域中的所有值或在作用域中使用的所有值都被称为在该作用域中的“可达”,并被称为“可达值”。可访问的值总是存储在内存中。

在以下情况下,值被认为是可达的:

  • 程序根中的值或从根中引用的值,如全局变量或当前执行的函数、它的上下文和回调。
  • 通过引用或引用链从根中访问的值(例如,全局变量中的对象引用另一个对象,该对象也引用另一个对象——这些都被认为是可访问的值)。

下面的代码片段说明了可达性的概念:

var person = {name: "xiaan"};

这里我们有一个对象,它的键值对(name"xiaan")引用全局变量 person。如果我们通过赋值 null 来覆盖 person 的值:

person = null;

那么对象将被垃圾回收,"xiaan" 值将无法再次访问。下面是另一个例子:

var person = {name: "xiaan"};
var programmer = person;

从上面的代码片段中,我们可以从 person 变量和 programmer 变量访问 object 属性。然而,如果我们将person 设置为 null

person = null;

那么对象仍然在内存中,因为它可以通过 programmer 变量访问。简单地说,这就是垃圾收集的工作方式。

注意:默认情况下,JavaScript 对其引用使用强引用。要在 JavaScript 中实现弱引用,可以使用 WeakMapWeakSetWeakRef

4. Set VS WeakSet

set 对象是一个只有一次出现的唯一值的集合。像数组一样,集合没有键值对。我们可以使用for...of.forEach 的数组方法遍历。

让我们用以下片段来说明这一点:

let setArray = new Set(["Joseph", "Frank", "John", "Davies"]);
for (let names of setArray){
  console.log(names)
}// Joseph Frank John Davies

我们也可以使用 .forEach 遍历:

setArray.forEach((name, nameAgain, setArray) =>{
   console.log(names);
 });

WeakSet 是唯一对象的集合。正如其名称一样,弱集使用弱引用。以下是 WeakSet() 的特性:

  • 它可能只包含对象。
  • 集合中的对象可以在其他地方访问。
  • 它不能循环遍历。
  • Set() 一样,WeakSet()addhasdelete 方法。

下面的代码说明了如何使用 WeakSet() 和一些可用的方法:

const human = new WeakSet();
let person = {name: "xiaan"};
human.add(person);
console.log(human.has(person)); // true
person = null;
console.log(human.has(person)); // false

在第 1 行,我们创建了 WeakSet() 的一个实例。在第 3 行,我们创建了对象并将它分配给变量 person。在第 5 行,我们将 person 添加到 WeakSet() 中。在第 9 行,我们将 person 引用设为空。第 11 行代码返回false,因为 WeakSet() 将被自动清除,因此,WeakSet() 不会阻止垃圾回收。

5. Map VS WeakMap

我们从上面关于垃圾收集的部分了解到,只要可以访问,JavaScript 引擎就会在内存中保留一个值。让我们用一些片段来说明这一点:

let person = {name: "xiaan"};
// 对象可以从引用中访问
// 覆盖引用 person.
person = null;
// 该对象不能被访问

当数据结构在内存中时,数据结构的属性被认为是可访问的,并且它们通常保存在内存中。如果将对象存储在数组中,那么只要数组在内存中,即使没有其他引用,也仍然可以访问对象。

let person = {name: "xiaan"};
let arr = [person];
// 覆盖引用
person = null;
console.log(array[0]) // {name: 'xiaan'}

即使引用被覆盖,我们仍然能够访问这个对象因为对象被保存在数组中。因此,只要数组仍然在内存中,它就保存在内存中。因此,它没有被垃圾回收。由于我们在上面的例子中使用了数组,我们也可以使用 map。当 map 仍然存在时,存储在其中的值将不会被垃圾回收。

let map = new Map();
let person = {name: "xiaan"};
map.set(person, "person");
// 覆盖引用
person = null;
// 还能访问对象
console.log(map.keys());

与对象一样,map 可以保存键—值对,我们可以通过键访问值。但是对于 map,我们必须使用 .get() 方法来访问值。

根据 Mozilla Developer Network,Map 对象保存键—值对并记住键的原始插入顺序。任何值(包括对象值和原语值)都可以用作键或值。

map 不同,WeakMap 保存弱引用。因此,如果这些值在其他地方没有被强引用,它不会阻止垃圾回收删除它引用的值。除此之外,WeakMapmap 是相同的。由于弱引用,WeakMap 不可枚举。

对于 WeakMap,键必须是对象,值可以是数字或字符串。

下面的代码片段说明了 WeakMap 的工作原理和其中的方法:

// 创建一个 WeakMap
let weakMap = new WeakMap();
let weakMap2 = new WeakMap();
// 创建一个对象
let ob = {};
// 使用 set 方法
weakMap.set(ob, "Done");
// 你可以将值设置为一个对象甚至一个函数
weakMap.set(ob, ob)
// 可以设置为undefined
weakMap.set(ob, undefined);
// WeakMap 也可以是值和键
weakMap.set(weakMap2, weakMap)
// 要获取值,使用 get 方法
weakMap.get(ob) // Done
// 使用 has 方法
weakMap.has(ob) // true
weakMap.delete(ob)
weakMap.has(ob) // false

在没有其他引用的 WeakMap 中使用对象作为键的一个主要副作用是,它们将在垃圾收集期间自动从内存中删除。

6.「WeakMap 的应用」

WeakMap 可以用于 web 开发的两个领域:缓存和额外的数据存储。

6.1 缓存

这是一种 web 技术,它涉及到保存(即存储)给定资源的副本,并在请求时返回它。可以缓存函数的结果,以便在调用函数时重用缓存的结果。

让我们来看看实际情况。

let cachedResult = new WeakMap();
// 存储结果的函数
function keep(obj){
 if(!cachedResult.has(obj){
    let result = obj;
    cachedResult.set(obj, result);
  }
 return cachedResult.get(obj);
}
let obj = {name: "xiaan"};
let resultSaved = keep(obj)
obj = null;
// console.log(cachedResult.size); Possible with map, not with WeakMap

如果我们在上面的代码中使用 Map() 而不是 WeakMap(),并且对 keep() 函数有多次调用,那么它只会在第一次调用时计算结果,并在其他时候从 cachedResult 检索结果。副作用是,每当对象不需要时,我们就需要清理 cachedResult。使用 WeakMap(),一旦对象被垃圾回收,缓存的结果就会自动从内存中删除。缓存是提高软件性能的一种很好的方法——它可以节省数据库使用、第三方 API 调用和服务器对服务器请求的成本。通过缓存,请求结果的副本被保存在本地。

6.2 额外的数据存储

WeakMap() 的另一个重要用途是额外的数据存储。想象一下,我们正在建立一个电子商务平台,我们有一个计算访客数量的程序,我们希望能够在访客离开时减少计数。这个任务在 Map 中要求很高,但在 WeakMap() 中很容易实现:

let visitorCount = new WeakMap();
function countCustomer(customer){
  let count = visitorCount.get(customer) || 0;
  visitorCount.set(customer, count + 1);
}
let person = {name: "xiaan"};
// 统计访问人数
countCustomer(person)
// 访客离开
person = null;

使用 Map(),我们必须在客户离开时清除 visitorCount,否则,它将在内存中无限增长,占用空间。但是使用 WeakMap(),我们不需要清理 visitorCount,一旦一个人(对象)变得不可访问,它就会自动被垃圾回收。

7. 小结

在本文中,我们了解了弱引用、强引用和可达性的概念,并尽可能地将它们与内存管理联系起来。

相关文章
|
API PHP 数据库
Laravel框架下通过DB获取数据并转为数组的方法
通过上述方法,Laravel为开发者提供了一套灵活而强大的工具,用于从数据库中检索数据并将其转换为数组。无论是使用DB Facade直接执行查询,还是利用模型的方法,Laravel都能够简化这一过程,使得代码既简洁又富有表现力。在实际开发中,选择最适合你需求的方法可以有效提高开发效率和应用性能。
429 0
|
数据采集 弹性计算 供应链
阿里云服务器付费模式:按量付费、包年包月和抢占式实例全解析
阿里云服务器提供包年包月、按量付费与抢占式实例三种付费模式。包年包月为预付费,适合长期稳定使用,价格更优惠并支持备案。按量付费则为后付费模式,按小时结算,适合短期或访问量波动大的场景,但不支持备案。抢占式实例基于按量付费,价格更低(最多节省90%),适用于无状态应用,如临时测试或可弹性伸缩的Web服务,但存在被系统释放的风险,同样不支持备案。根据具体需求选择合适的付费模式能够有效降低成本并提高效率。
886 1
|
监控 API 数据安全/隐私保护
小红书详情API接口的获取与应用
在互联网信息爆炸的时代,小红书凭借丰富的用户生成内容(UGC)和精准的推荐系统迅速崛起,成为重要的社区电商平台。为了帮助开发者高效利用平台数据,小红书开放平台提供了多种API接口,涵盖商品详情和笔记详情等。本文详细介绍了如何注册、申请权限、构建请求、处理响应及应用这些API接口,旨在为开发者提供全面的指南,助力数据驱动的决策与创新。
5523 1
|
JavaScript 前端开发 编译器
vue 代码高亮 prismjs 或 highlight.js插件的用法
vue 代码高亮 prismjs 或 highlight.js插件的用法
971 0
|
运维 Linux
在Linux中,如何排查硬件故障?
在Linux中,如何排查硬件故障?
|
存储 监控 Java
JVM 元空间(Metaspace)
JVM 元空间(Metaspace)
1626 5
|
机器学习/深度学习 数据采集 运维
Python基于孤立森林算法(IsolationForest)实现数据异常值检测项目实战
Python基于孤立森林算法(IsolationForest)实现数据异常值检测项目实战
|
存储 应用服务中间件 nginx
nginx日志定时切割 按年月日
nginx日志定时切割 按年月日
208 0
|
Java Spring
解决org.yaml.snakeyaml.error.YAMLException: java.nio.charset.MalformedInputException: Input length = 2
解决org.yaml.snakeyaml.error.YAMLException: java.nio.charset.MalformedInputException: Input length = 2
1440 1
|
存储 安全 API
深入理解OAuth 2.0:授权的标准化之路
在现代的互联网应用中,安全的身份验证和授权是至关重要的。OAuth 2.0作为一个流行的开放标准,用于授权第三方应用访问用户数据,而无需将用户的凭据(用户名和密码)传递给第三方。本文将深入探讨OAuth 2.0的基本概念、工作流程、角色、授权类型,以及如何在实际应用中应用OAuth 2.0来保障应用的安全性和用户隐私。
450 0