内存和性能管理是软件开发的重要方面,也是每个软件开发者都应该注意的。虽然很有用,但弱引用在JavaScript中并不经常使用。WeakSet 和WeakMap 是在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中实现弱引用,你可以使用WeakMap 、WeakSet 、或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 持有弱引用;因此,如果它引用的值在其他地方没有被强引用,它就不能阻止垃圾收集删除这些值。除此之外,WeakMap 和map 是一样的。由于弱引用,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 ;只要一个人(对象)变得不可联系,它就会被自动收集垃圾。
总结
在这篇文章中,我们了解了弱引用、强引用和可及性的概念,并试图尽可能地将它们与内存管理联系起来。我希望你能发现这篇文章的价值。请随时发表评论。