Heim  >  Artikel  >  Web-Frontend  >  Ausführliche Erklärung der Speicherverwaltung in JavaScript

Ausführliche Erklärung der Speicherverwaltung in JavaScript

青灯夜游
青灯夜游nach vorne
2021-01-06 10:14:352165Durchsuche

Ausführliche Erklärung der Speicherverwaltung in JavaScript

Verwandte Empfehlungen: „Javascript-Video-Tutorial

Meistens entwickeln wir einfach, ohne die Kenntnisse über die Speicherverwaltung zu verstehen, da die JS-Engine dieses Problem für uns erledigt. Manchmal stoßen wir jedoch auf Probleme wie Speicherlecks. Nur wenn wir wissen, wie die Speicherzuweisung funktioniert, können wir diese Probleme lösen.

In diesem Artikel stellen wir hauptsächlich vor, wie Speicherzuweisung und Speicherbereinigung funktionieren und wie man einige häufige SpeicherlecksProbleme vermeidet.

Cache (Speicher)-Lebenszyklus

Wenn wir in JS eine Variable, eine Funktion oder ein beliebiges Objekt erstellen, weist die JS-Engine Speicher dafür zu und gibt ihn frei, wenn er nicht mehr benötigt wird.

Speicher zuweisen ist der Vorgang, bei dem Speicherplatz im Speicher reserviert wird, während Speicher freigeben Speicherplatz freigibt, um ihn für andere Zwecke vorzubereiten.

Jedes Mal, wenn wir eine Variable zuweisen oder eine Funktion erstellen, durchläuft die Speicherung dieser Variablen die gleichen Phasen:

Ausführliche Erklärung der Speicherverwaltung in JavaScript

Speicher zuweisen

  • JS übernimmt dies für uns: Es weist dem von uns erstellten Objekt den erforderlichen Speicher zu .

Speicher verwenden

  • Speicher verwenden ist etwas, was wir explizit im Code tun: Das Lesen und Schreiben von Speicher ist eigentlich das Lesen und Schreiben von Variablen.

Speicher freigeben

  • Dieser Schritt wird auch von der JS-Engine übernommen. Sobald der zugewiesene Speicher freigegeben ist, kann er für neue Zwecke verwendet werden.

„Objekt“ im Kontext der Speicherverwaltung umfasst nicht nur JS-Objekte, sondern auch Funktionen und Funktionsumfänge.

Speicher-Heap und -Stack

Jetzt wissen wir, dass die Engine für alles, was wir in JS definieren, Speicher zuweist und ihn freigibt, wenn er nicht mehr benötigt wird.

Die nächste Frage, die mir in den Sinn kommt, ist: Wo werden diese Dinge aufbewahrt? Die

JS-Engine kann Daten an zwei Orten speichern: Memory Heap und Stack. Heap und Stack sind zwei Datenstrukturen, die von der Engine für unterschiedliche Zwecke verwendet werden.

Stack: Statische Speicherzuweisung

Ausführliche Erklärung der Speicherverwaltung in JavaScript

Der Stack ist eine Datenstruktur, die von JS zum Speichern statischer Daten verwendet wird. Statische Daten sind Daten, deren Größe die Engine zum Zeitpunkt der Kompilierung kennt. In JS gehören dazu primitive Werte (stringsnumberbooleanundefinednull) und Referenztypen, die auf Objekte und Funktionen verweisen.

Da die Engine weiß, dass sich die Größe nicht ändert, weist sie jedem Wert eine feste Speichermenge zu.

Der Prozess der Speicherzuweisung unmittelbar vor der Ausführung wird als statische Speicherzuweisung bezeichnet. Die Grenzen dieser Werte und des gesamten Stapels sind browserabhängig.

Heap: Dynamische Speicherzuweisung

Der Heap ist ein weiterer Bereich, in dem Daten gespeichert werden, in dem JS Objekte und Funktionen speichert.

Im Gegensatz zum Stack weist die JS-Engine diesen Objekten keine feste Speichermenge zu, sondern weist den Speicherplatz nach Bedarf zu. Diese Art der Speicherzuweisung wird auch „Dynamic Memory Allocation“ genannt. Die Eigenschaften dieser beiden Speicher werden unten verglichen:

Stack Speichert grundlegende Typen und Referenzen Größe ist zur Kompilierzeit bekannt Die Größe ist nur zur Laufzeit bekannt

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 将对象函数存储在堆中。 基本类型和引用存储在堆栈中。

Ausführliche Erklärung der Speicherverwaltung in JavaScript

这张照片中,我们可以观察到如何存储不同的值。 注意personnewPerson都如何指向同一对象。

事例

const person = {
  name: 'John',
  age: 24,
};

这将在堆中创建一个新对象,并在堆栈中创建对该对象的引用。

垃圾回收

现在,我们知道 JS 如何为各种对象分配内存,但是在内存生命周期,还有最后一步:释放内存

就像内存分配一样,JavaScript引擎也为我们处理这一步骤。 更具体地说,垃圾收集器负责此工作。

一旦 JS 引擎识别变量或函数不在被需要时,它就会释放它所占用的内存。

这样做的主要问题是,是否仍然需要一些内存是一个无法确定的问题,这意味着不可能有一种算法能够在不再需要那一刻立即收集不再需要的所有内存。

一些算法可以很好地解决这个问题。 我将在本节中讨论最常用的方法:引用计数标记清除算法。

引用计数

当声明了一个变量并将一个引用类型值赋值该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另外一个变量,则该值得引用次数加1。相反,如果包含对这个值引用的变量又取 得了另外一个值,则这个值的引用次数减 1

当这个值的引用次数变成 0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾收集器下次再运行时,它就会释放那 些引用次数为零的值所占用的内存。

我们看下面的例子。

Ausführliche Erklärung der Speicherverwaltung in JavaScript

请注意,在最后一帧中,只有hobbies留在堆中的,因为最后引用的是对象。

周期数

引用计数算法的问题在于它不考虑循环引用。 当一个或多个对象互相引用但无法再通过代码访问它们时,就会发生这种情况。

let son = {
  name: 'John',
};

let dad = {
  name: 'Johnson',
}

son.dad = dad;
dad.son = son;

son = null;
dad = null;

Ausführliche Erklärung der Speicherverwaltung in JavaScript

由于父对象相互引用,因此该算法不会释放分配的内存,我们再也无法访问这两个对象。

它们设置为null不会使引用计数算法识别出它们不再被使用,因为它们都有传入的引用。

标记清除

标记清除算法对循环依赖性有解决方案。 它检测到是否可以从root 对象访问它们,而不是简单地计算对给定对象的引用。

浏览器的rootwindow 对象,而NodeJS中的rootglobal

Ausführliche Erklärung der Speicherverwaltung in JavaScript

该算法将无法访问的对象标记为垃圾,然后对其进行扫描(收集)。 根对象将永远不会被收集。

这样,循环依赖关系就不再是问题了。在前面的示例中,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.

Ausführliche Erklärung der Speicherverwaltung in JavaScript

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. 🎜🎜Ausführliche Erklärung der Speicherverwaltung in JavaScript🎜🎜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;
🎜Ausführliche Erklärung der Speicherverwaltung in JavaScript🎜🎜 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. 🎜🎜Ausführliche Erklärung der Speicherverwaltung in JavaScript🎜🎜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 中,如果省略varconstlet,则变量会被加到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

更多编程相关知识,请访问:编程入门!!

Heap
Weist eine feste Menge an Speicher zu

Objekt Und Funktion
Es gibt keine Einschränkungen

Das obige ist der detaillierte Inhalt vonAusführliche Erklärung der Speicherverwaltung in JavaScript. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:segmentfault.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen