了解JavaScript中的弱引用

157 阅读9分钟

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

弱引用

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

在进入强引用之前,WeakSet,Set,WeakMap, 和Map ,让我们用下面的片段来说明弱引用。

// Create an instance of the WeakMap object.
let human = new WeakMap():

// Create an object, and assign it to a variable called man.
let man = { name: "Joe Doe" };

// Call the set method on human, and pass two arguments (key and value) to it.
human.set(man, "done")

console.log(human)

上述代码的输出将是以下内容。

WeakMap {{…} => 'done'}

man = null;
console.log(human)

man 参数现在被设置为WeakMap 对象。在我们将man 变量重新赋值给null 的时候,内存中对原始对象的唯一引用是弱引用,它来自我们之前创建的WeakMap 。当JavaScript引擎运行一个垃圾收集过程时,man 对象将从内存和我们分配给它的WeakMap 中删除。这是因为它是一个弱引用,它不能阻止垃圾收集。

看起来我们正在取得进展。让我们来谈谈强引用,然后我们再把所有的东西联系起来。

强大的参考资料

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

下面的代码片断说明了强引用的概念。

let man = {name: "Joe Doe"};

let human = [man];

man =  null;
console.log(human);

上面代码的结果会是这样的。

// An array of objects of length 1. 
[{…}]

由于human 数组和对象之间存在强引用,该对象不能再通过dog 变量进行访问。该对象被保留在内存中,可以通过以下代码进行访问。

console.log(human[0])

这里需要注意的是,弱引用并不能阻止一个对象被垃圾收集,而强引用可以阻止一个对象被垃圾收集。

JavaScript中的垃圾收集

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

可及性

在一个特定的作用域中的所有值,或者在一个作用域中正在使用的值,在该作用域中被称为 "可达",并被称为 "可达值"。可到达的值总是存储在内存中。

如果是这样的值就被认为是可及的。

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

下面的代码片断说明了可及性的概念。

let languages = {name: “JavaScript”};

这里我们有一个对象,它有一个键值对(名称为JavaScript ),引用全局变量languages 。如果我们通过将null 覆盖languages 的值...

languages = null;

......那么这个对象将被垃圾回收,并且不能再次访问JavaScript 。下面是另一个例子。

let languages = {name: “JavaScript”};

let programmer = languages;

从上面的代码片断中,我们可以从languages 变量和programmer 变量中访问对象属性。然而,如果我们把languages 设为null...

languages = null;

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

注意。 默认情况下,JavaScript的引用使用强引用。要在JavaScript中实现弱引用,你可以使用WeakMapWeakSet 、或WeakRef

比较Set和WeakSet

一个集合对象是一个唯一值的集合,只有一次出现。一个集合,像一个数组一样,没有键-值对。我们可以用数组方法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 是一个唯一对象的集合。正如其名,WeakSets使用弱引用。以下是WeakSet() 的属性。

  • 它只能包含对象。
  • 集合内的对象可以在其他地方到达。
  • 它不能被循环使用。
  • Set() 一样,WeakSet() 也有方法add,has, 和delete

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

const human = new WeakSet();

let paul = {name: "Paul"};
let mary = {gender: "Mary"};

// Add the human with the name paul to the classroom. 
const classroom = human.add(paul);

console.log(classroom.has(paul)); // true

paul = null;

// The classroom will be cleaned automatically of the human paul.

console.log(classroom.has(paul)); // false

在第1行,我们创建了一个WeakSet() 的实例。在第3行和第4行,我们创建了对象并将它们分配给各自的变量。在第7行,我们将paul 添加到WeakSet() ,并将其分配给classroom 变量。在第11行,我们使paul 引用null 。第15行的代码返回false ,因为WeakSet() 将被自动清理;所以,WeakSet() 并不妨碍垃圾收集。

比较Map和WeakMap

正如我们在上面关于垃圾收集的章节中所知道的,只要一个值是可触及的,JavaScript引擎就会把它保留在内存中。让我们用一些片段来说明这一点。

let smashing = {name: "magazine"};
// The object can be accessed from the reference.

// Overwrite the reference smashing.
smashing = null;
// The object can no longer be accessed.

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

let smashing = {name: "magazine"};

let arr = [smashing];

// Overwrite the reference.
smashing = null;
console.log(array[0]) // {name: 'magazine'}

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

let map = new Map();

let smashing {name: "magazine"};

map.set(smashing, "blog");

// Overwrite the reference.
smashing = null;

// To access the object.
console.log(map.keys());

像一个对象一样,maps可以保存键-值对,我们可以通过键来访问值。但是对于maps,我们必须使用.get() 方法来访问这些值。

根据Mozilla开发者网络的说法,Map 对象持有键值对,并记住键的原始插入顺序。任何值(包括对象和原始值)都可以作为键或值使用。

map 不同,WeakMap 持有弱引用;因此,如果它引用的值在其他地方没有被强引用,它就不能阻止垃圾收集删除这些值。除此之外,WeakMapmap 是一样的。由于弱引用,WeakMaps 是不可列举的。

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

下面的片段说明了WeakMap 是如何工作的以及其中的方法。

// Create a weakMap.
let weakMap = new WeakMap();

let weakMap2 = new WeakMap();

// Create an object.
let ob = {};

// Use the set method.
weakMap.set(ob, "Done");

// You can set the value to be an object or even a function.
weakMap.set(ob, ob)

// You can set the value to undefined.
weakMap.set(ob, undefined);

// WeakMap can also be the value and the key.
weakMap.set(weakMap2, weakMap)

// To get values, use the get method.
weakMap.get(ob) // Done

// Use the has method.
weakMap.has(ob) // true

weakMap.delete(ob)

weakMap.has(ob) // false

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

WeakMap的应用范围

WeakMap 在Web开发的两个领域中可以使用 "缓存 "和额外的数据存储。

缓存

这是一种网络技术,包括保存(即存储)一个给定资源的副本,并在请求时将其送回。一个函数的结果可以被缓存,这样每当函数被调用时,缓存的结果可以被重新使用。

让我们来看看这个动作。创建一个文件,将其命名为cachedResult.js ,并在其中写下以下内容。

 let cachedResult = new WeakMap();
 // A function that stores a result.
function keep(obj){
if(!cachedResult.has(obj){
  let result = obj;
  cachedResult.set(obj, result);
  }
return cachedResult.get(obj);
}


let obj = {name: "Frank"};

let resultSaved = keep(obj)

obj = null;

// console.log(cachedResult.size); Possible with map, not with WeakMap

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

额外的数据

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

let visitorCount = new WeakMap();
function countCustomer(customer){
   let count = visitorCount.get(customer) || 0;
    visitorCount.set(customer, count + 1);
}

让我们为此创建客户端代码。

let person = {name: "Frank"};

// Taking count of person visit.
countCustomer(person)

// Person leaves.
person = null;

使用Map() ,每当有客户离开时,我们就必须清理visitorCount ;否则,它将在内存中无限地增长,占用空间。但是有了WeakMap() ,我们就不需要清理visitorCount ;只要一个人(对象)变得不可联系,它就会被自动收集垃圾。

总结

在这篇文章中,我们了解了弱引用、强引用和可及性的概念,并试图尽可能地将它们与内存管理联系起来。我希望你能发现这篇文章的价值。请随时发表评论。