Speichert grundlegende Typen und Referenzen Größe ist zur Kompilierzeit bekannt Weist eine feste Menge an Speicher zu
Objekt Und Funktion | Die Größe ist nur zur Laufzeit bekannt Es gibt keine Einschränkungen
|
Beispiele
Nehmen wir ein paar Beispiele, um das Bild zu verbessern.
const person = {
name: 'John',
age: 24,
};
JS reserviert Speicher für dieses Objekt im Heap. Die tatsächlichen Werte sind immer noch die Originalwerte, deshalb werden sie auf dem Stapel gespeichert.
const hobbies = ['hiking', 'reading'];
Arrays sind auch Objekte, deshalb werden sie im Heap gespeichert.
let name = 'John'; // 为字符串分配内存
const age = 24; // 为字分配内存
name = 'John Doe'; // 为新字符串分配内存
const firstName = name.slice(0,4); // 为新字符串分配内存
Der Anfangswert ist unveränderlich, sodass JS den ursprünglichen Wert nicht ändert, sondern einen neuen erstellt.
Referenzen in JavaScript
Alle Variablen verweisen zunächst auf den Stapel. Wenn es sich um einen nicht-primitiven Wert handelt, enthält der Stack
einen Verweis auf das Objekt im Heap
. 堆栈
包含对堆
中对象的引用。
堆的内存没有按特定的方式排序,所以我们需要在堆栈中保留对其的引用。 我们可以将引用
视为地址,并将堆中的对象视为这些地址所属的房屋。
请记住,JS 将对象和函数存储在堆中。 基本类型和引用存储在堆栈中。
这张照片中,我们可以观察到如何存储不同的值。 注意person
和newPerson
都如何指向同一对象。
事例
const person = {
name: 'John',
age: 24,
};
这将在堆中创建一个新对象,并在堆栈中创建对该对象的引用。
垃圾回收
现在,我们知道 JS 如何为各种对象分配内存,但是在内存生命周期,还有最后一步:释放内存。
就像内存分配一样,JavaScript引擎也为我们处理这一步骤。 更具体地说,垃圾收集器负责此工作。
一旦 JS 引擎识别变量或函数不在被需要时,它就会释放它所占用的内存。
这样做的主要问题是,是否仍然需要一些内存是一个无法确定的问题,这意味着不可能有一种算法能够在不再需要那一刻立即收集不再需要的所有内存。
一些算法可以很好地解决这个问题。 我将在本节中讨论最常用的方法:引用计数
和标记清除
算法。
引用计数
当声明了一个变量并将一个引用类型值赋值该变量时,则这个值的引用次数就是1
。如果同一个值又被赋给另外一个变量,则该值得引用次数加1
。相反,如果包含对这个值引用的变量又取 得了另外一个值,则这个值的引用次数减 1
。
当这个值的引用次数变成 0
时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾收集器下次再运行时,它就会释放那 些引用次数为零的值所占用的内存。
我们看下面的例子。
请注意,在最后一帧中,只有hobbies
留在堆中的,因为最后引用的是对象。
周期数
引用计数
算法的问题在于它不考虑循环引用。 当一个或多个对象互相引用但无法再通过代码访问它们时,就会发生这种情况。
let son = {
name: 'John',
};
let dad = {
name: 'Johnson',
}
son.dad = dad;
dad.son = son;
son = null;
dad = null;
由于父对象相互引用,因此该算法不会释放分配的内存,我们再也无法访问这两个对象。
它们设置为null
不会使引用计数算法识别出它们不再被使用,因为它们都有传入的引用。
标记清除
标记清除算法对循环依赖性有解决方案。 它检测到是否可以从root
对象访问它们,而不是简单地计算对给定对象的引用。
浏览器的root
是window
对象,而NodeJS中的root
是global
。
该算法将无法访问的对象标记为垃圾,然后对其进行扫描(收集)。 根对象将永远不会被收集。
这样,循环依赖关系就不再是问题了。在前面的示例中,dad
对象和son
Der Speicher des Heaps ist nicht auf eine bestimmte Weise geordnet, daher müssen wir einen Verweis darauf im Stapel behalten. Wir können uns Referenzen
als Adressen vorstellen und die Objekte im Heap als die Häuser, zu denen diese Adressen gehören. Denken Sie daran, dass JS Objekte
und Funktionen
auf dem Heap speichert. Primitive Typen und Referenzen werden auf dem Stapel gespeichert.
Dies Auf dem Foto können wir beobachten, wie unterschiedliche Werte gespeichert werden. Beachten Sie, dass person
und newPerson
beide auf dasselbe Objekt verweisen. Beispiel🎜users = getUsers();
🎜Dadurch wird ein neues Objekt im Heap und eine Referenz auf das Objekt im Stapel erstellt. 🎜🎜Garbage Collection🎜🎜Jetzt wissen wir, wie JS Speicher für verschiedene Objekte zuweist, aber im Speicherlebenszyklus gibt es einen letzten Schritt: 🎜Speicher freigeben🎜. 🎜🎜Genau wie die Speicherzuweisung übernimmt auch dieser Schritt die JavaScript-Engine für uns. Genauer gesagt ist dafür der 🎜Garbage Collector🎜 verantwortlich. 🎜🎜Sobald die JS-Engine erkennt, dass eine Variable oder Funktion nicht mehr benötigt wird, gibt sie den von ihr belegten Speicher frei. 🎜🎜Das Hauptproblem dabei ist, dass die Frage, ob noch etwas Speicher benötigt wird, eine unentscheidbare Frage ist, was bedeutet, dass es unmöglich ist, einen Algorithmus zu haben, der sofort den gesamten nicht mehr benötigten Speicher sammeln kann, sobald er nicht mehr benötigt wird. 🎜🎜Einige Algorithmen können dieses Problem sehr gut lösen. In diesem Abschnitt bespreche ich die gängigsten Methoden: die Algorithmen Reference Counting
und Mark Sweep
. 🎜🎜Referenzanzahl🎜🎜Wenn eine Variable deklariert und der Variablen ein Referenztypwert zugewiesen wird, beträgt die Anzahl der Referenzen auf diesen Wert 1
. Wenn derselbe Wert einer anderen Variablen zugewiesen wird, wird der Referenzzähler des Werts um 1
erhöht. Wenn umgekehrt die Variable, die einen Verweis auf diesen Wert enthält, einen anderen Wert annimmt, wird die Anzahl der Verweise auf diesen Wert um 1
dekrementiert. 🎜🎜Wenn die Anzahl der Verweise auf diesen Wert 0
wird, bedeutet das, dass es keine Möglichkeit mehr gibt, auf diesen Wert zuzugreifen, sodass der von ihm belegte Speicherplatz zurückgewonnen werden kann. Auf diese Weise gibt der Garbage Collector bei der nächsten Ausführung den Speicher frei, der von Werten ohne Referenzen belegt ist. 🎜🎜Schauen wir uns das folgende Beispiel an. 🎜🎜🎜🎜Bitte Beachten Sie, dass im letzten Frame nur hobbys
auf dem Heap verbleiben, da der letzte Verweis auf das Objekt erfolgte. 🎜🎜Periodenzählung🎜🎜Das Problem mit dem Algorithmus Referenzzählung
besteht darin, dass er Zirkelverweise nicht berücksichtigt. Dies geschieht, wenn ein oder mehrere Objekte aufeinander verweisen, auf sie aber nicht mehr über Code zugegriffen werden kann. 🎜window.users = null;
🎜🎜🎜 Da die übergeordneten Objekte aufeinander verweisen, gibt der Algorithmus den zugewiesenen Speicher nicht frei und wir können nicht mehr auf beide Objekte zugreifen. 🎜🎜Wenn Sie sie auf null
setzen, erkennt der Referenzzählalgorithmus nicht, dass sie nicht mehr verwendet werden, da sie alle über eingehende Referenzen verfügen. 🎜🎜Mark and Sweep🎜🎜Der Mark and Sweep-Algorithmus bietet Lösungen für zyklische Abhängigkeiten. Es erkennt, ob sie vom root
-Objekt aus zugänglich sind, anstatt einfach den Verweis auf das angegebene Objekt zu berechnen. 🎜🎜Der root
des Browsers ist das window
-Objekt, während der root
in NodeJS global
ist. 🎜🎜🎜🎜Dies Der Algorithmus markiert nicht erreichbare Objekte als Müll und scannt (sammelt) sie dann. Das Stammobjekt wird niemals erfasst. 🎜🎜Dadurch sind zirkuläre Abhängigkeiten kein Thema mehr. Im vorherigen Beispiel ist weder auf das Objekt dad
noch auf das Objekt son
vom Stammverzeichnis aus zugreifbar. Daher werden sie alle als Müll markiert und eingesammelt. 🎜🎜Dieser Algorithmus ist seit 2012 in allen modernen Browsern implementiert. Lediglich Leistung und Implementierung wurden verbessert, die Kernidee des Algorithmus bleibt jedoch dieselbe. 🎜🎜Handel 🎜🎜Durch die automatische Speicherbereinigung können wir uns auf die Erstellung von Anwendungen konzentrieren, anstatt Zeit mit der Speicherverwaltung zu verschwenden. Allerdings gibt es Kompromisse. 🎜🎜Speichernutzung🎜🎜Da der Algorithmus nicht genau weiß, wann Speicher nicht mehr benötigt wird, kann eine JS-Anwendung mehr Speicher verwenden, als sie tatsächlich benötigt. 🎜🎜Auch wenn ein Objekt als Müll markiert ist, liegt es am Müllsammler, zu entscheiden, wann und ob der zugewiesene Speicher gesammelt wird. 🎜如果你希望应用程序尽可能提高内存效率,那么最好使用低级语言。 但是请记住,这需要权衡取舍。
性能
收集垃圾的算法通常会定期运行以清理未使用的对象。
问题是我们开发人员不知道何时会回收。 收集大量垃圾或频繁收集垃圾可能会影响性能。然而,用户或开发人员通常不会注意到这种影响。
内存泄漏
在全局变量中存储数据,最常见内存问题可能是内存泄漏。
在浏览器的 JS 中,如果省略var
,const
或let
,则变量会被加到window
对象中。
users = getUsers();
在严格模式下可以避免这种情况。
除了意外地将变量添加到根目录之外,在许多情况下,我们需要这样来使用全局变量,但是一旦不需要时,要记得手动的把它释放了。
释放它很简单,把 null
给它就行了。
window.users = null;
被遗忘的计时器和回调
忘记计时器和回调可以使我们的应用程序的内存使用量增加。 特别是在单页应用程序(SPA)中,在动态添加事件侦听器和回调时必须小心。
被遗忘的计时器
const object = {};
const intervalId = setInterval(function() {
// 这里使用的所有东西都无法收集直到清除`setInterval`
doSomething(object);
}, 2000);
上面的代码每2秒运行一次该函数。 如果我们的项目中有这样的代码,很有可能不需要一直运行它。
只要setInterval
没有被取消,则其中的引用对象就不会被垃圾回收。
确保在不再需要时清除它。
clearInterval(intervalId);
被遗忘的回调
假设我们向按钮添加了onclick
侦听器,之后该按钮将被删除。旧的浏览器无法收集侦听器,但是如今,这不再是问题。
不过,当我们不再需要事件侦听器时,删除它们仍然是一个好的做法。
const element = document.getElementById('button');
const onClick = () => alert('hi');
element.addEventListener('click', onClick);
element.removeEventListener('click', onClick);
element.parentNode.removeChild(element);
脱离DOM引用
内存泄漏与前面的内存泄漏类似:它发生在用 JS 存储DOM
元素时。
const elements = [];
const element = document.getElementById('button');
elements.push(element);
function removeAllElements() {
elements.forEach((item) => {
document.body.removeChild(document.getElementById(item.id))
});
}
删除这些元素时,我们还需要确保也从数组中删除该元素。否则,将无法收集这些DOM元素。
const elements = [];
const element = document.getElementById('button');
elements.push(element);
function removeAllElements() {
elements.forEach((item, index) => {
document.body.removeChild(document.getElementById(item.id));
elements.splice(index, 1);
});
}
由于每个DOM元素也保留对其父节点的引用,因此可以防止垃圾收集器收集元素的父元素和子元素。
总结
在本文中,我们总结了 JS 中内存管理的核心概念。写这篇文章可以帮助我们理清一些我们不完全理解的概念。
希望这篇对你有所帮助,我们下期再见,记得三连哦!
原文地址:https://felixgerschau.com/javascript-memory-management/
作者:Ahmad shaded
译文地址:https://segmentfault.com/a/1190000037651993
更多编程相关知识,请访问:编程入门!!