Maison  >  Article  >  interface Web  >  Explication détaillée de la gestion de la mémoire en JavaScript

Explication détaillée de la gestion de la mémoire en JavaScript

青灯夜游
青灯夜游avant
2021-01-06 10:14:352165parcourir

Explication détaillée de la gestion de la mémoire en JavaScript

Recommandations associées : "Tutoriel vidéo javascript"

La plupart du temps, nous développons uniquement sans comprendre les connaissances en matière de gestion de la mémoire. Le moteur JS s’en chargera pour nous. Cependant, nous rencontrons parfois des problèmes tels que des fuites de mémoire. Ce n'est qu'en connaissant le fonctionnement de l'allocation de mémoire que nous pouvons résoudre ces problèmes.

Dans cet article, nous présentons principalement les principes de fonctionnement de l'allocation de mémoire et du garbage collection et comment éviter certains problèmes courants de fuites de mémoire.

Cycle de vie du cache (mémoire)

Dans JS, lorsque nous créons une variable, une fonction ou tout autre objet, le moteur JS lui alloue de la mémoire et la libère lorsqu'elle n'est plus nécessaire.

Allocation de mémoire est le processus de réservation d'espace en mémoire, tandis que Libérer de la mémoire libère de l'espace pour le préparer à d'autres fins.

Chaque fois que nous allouons une variable ou créons une fonction, le stockage de cette variable passe par les mêmes étapes :

Explication détaillée de la gestion de la mémoire en JavaScript

Allouer de la mémoire

  • JS gère cela pour nous : il alloue la mémoire dont nous avons besoin pour créer l'objet.

Utiliser la mémoire

  • L'utilisation de la mémoire est quelque chose que nous faisons explicitement dans le code : la lecture et l'écriture de la mémoire sont en fait l'utilisation de variables Read et. écrire.

Libérer la mémoire

  • Cette étape est également gérée par le moteur JS, une fois la mémoire allouée libérée, elle peut être utilisée à de nouvelles fins .

« Objet » dans le contexte de la gestion de la mémoire inclut non seulement les objets JS, mais également les fonctions et les étendues de fonctions.

Memory Heap and Stack

Nous savons désormais que pour tout ce que nous définissons dans JS, le moteur alloue de la mémoire et la libère lorsqu'elle n'est plus nécessaire.

La prochaine question qui me vient à l’esprit est : où ces choses seront-elles stockées ?

Le moteur JS peut stocker des données à deux endroits : tas de mémoire et pile. Le tas et la pile sont deux structures de données utilisées par le moteur à des fins différentes.

Pile : allocation de mémoire statique

Explication détaillée de la gestion de la mémoire en JavaScript

La pile est une structure de données utilisée par JS pour stocker des données statiques. Les données statiques sont des données dont le moteur connaît la taille au moment de la compilation. En JS, cela inclut les valeurs primitives (strings, number, boolean, undefined et null) et les types de référence qui pointent vers des objets et des fonctions.

Puisque le moteur sait que la taille ne changera pas, il allouera une quantité fixe de mémoire pour chaque valeur.

Le processus d'allocation de mémoire immédiatement avant l'exécution est appelé allocation de mémoire statique. Les limites de ces valeurs et de l'ensemble de la pile dépendent du navigateur.

Heap : Allocation dynamique de mémoire

Heap est un autre espace où les données sont stockées, où JS stocke les objets et les fonctions .

Contrairement à la pile, le moteur JS n'alloue pas une quantité fixe de mémoire pour ces objets, mais alloue de l'espace selon les besoins. Cette façon d'allouer de la mémoire est également appelée allocation dynamique de mémoire.

Les caractéristiques de ces deux magasins seront comparées ci-dessous :

堆栈
存放基本类型和引用
大小在编译时已知
分配固定数量的内存
对象和函数
在运行时才知道大小
没怎么限制

Exemples

Prenons quelques exemples pour valoriser l’image.

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

JS alloue de la mémoire pour cet objet dans le tas. Les valeurs réelles sont toujours les valeurs d'origine, c'est pourquoi elles sont stockées sur la pile.

const hobbies = ['hiking', 'reading'];

Les tableaux sont aussi des objets, c'est pourquoi ils sont stockés dans le tas.

let name = 'John'; // 为字符串分配内存
const age = 24; // 为字分配内存

name = 'John Doe'; // 为新字符串分配内存
const firstName = name.slice(0,4); // 为新字符串分配内存

La valeur d'origine est immuable, donc JS ne modifie pas la valeur d'origine, mais en crée une nouvelle.

Références en JavaScript

Toutes les variables pointent d'abord vers la pile. S'il s'agit d'une valeur non primitive, 堆栈 contient une référence à l'objet dans .

La mémoire du tas n'est pas triée de manière spécifique, nous devons donc en conserver une référence sur la pile. Nous pouvons considérer 引用 comme des adresses, et les objets dans le tas comme les maisons auxquelles appartiennent ces adresses.

Rappelez-vous que JS stocke les objets et les fonctions sur le tas. Les types primitifs et les références sont stockés sur la pile.

Explication détaillée de la gestion de la mémoire en JavaScript

Sur cette photo, nous pouvons observer comment différentes valeurs sont stockées. Remarquez comment person et newPerson pointent vers le même objet.

Exemple

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

Cela créera un nouvel objet dans le tas et une référence à l'objet sur la pile.

Garbage Collection

Maintenant, nous savons comment JS alloue de la mémoire pour divers objets, mais dans le cycle de vie de la mémoire, il y a une dernière étape : Libérer la mémoire.

Tout comme l'allocation de mémoire, le moteur JavaScript gère également cette étape pour nous. Plus précisément, le Garbage Collector en est responsable.

Une fois que le moteur JS reconnaît qu'une variable ou une fonction n'est plus nécessaire, il libère la mémoire qu'elle occupait.

Le principal problème est qu'il est indécis de savoir si de la mémoire est encore nécessaire, ce qui signifie qu'il est impossible d'avoir un algorithme capable de collecter immédiatement toute la mémoire qui n'est plus nécessaire au moment où elle l'est. n'est plus nécessaire.

Certains algorithmes peuvent très bien résoudre ce problème. J'aborderai les méthodes les plus couramment utilisées dans cette section : les algorithmes 引用计数 et 标记清除.

Nombre de références

Lorsqu'une variable est déclarée et qu'une valeur de type référence est attribuée à la variable, le nombre de références à cette valeur est 1. Si la même valeur est attribuée à une autre variable, le nombre de références à la valeur est augmenté de 1. A l'inverse, si la variable contenant une référence à cette valeur obtient une autre valeur, le nombre de références à cette valeur est réduit de 1.

Lorsque le nombre de références à cette valeur devient 0, cela signifie qu'il n'y a plus moyen d'accéder à cette valeur, donc l'espace mémoire qu'elle occupe peut être récupéré. De cette façon, la prochaine fois que le garbage collector s'exécutera, il libérera la mémoire occupée par les valeurs sans références.

Regardons l'exemple ci-dessous.

Explication détaillée de la gestion de la mémoire en JavaScript

Veuillez noter que dans la dernière image, seul hobbies est laissé dans le tas, car la dernière référence est l'objet.

Nombre de cycles

引用计数Le problème avec l'algorithme est qu'il ne prend pas en compte les références circulaires. Cela se produit lorsqu'un ou plusieurs objets se réfèrent les uns aux autres mais qu'ils ne sont plus accessibles via le code.

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

let dad = {
  name: 'Johnson',
}

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

son = null;
dad = null;

Explication détaillée de la gestion de la mémoire en JavaScript

Puisque les objets parents font référence les uns aux autres, l'algorithme ne libère pas la mémoire allouée et on ne peut plus accéder aux deux objets.

Les définir sur null ne fera pas reconnaître par l'algorithme de comptage de références qu'elles ne sont plus utilisées, car elles ont toutes des références entrantes.

Marquer et balayer

L'algorithme de marquage et de balayage propose des solutions pour les dépendances cycliques. Il détecte s'ils sont accessibles depuis root objets au lieu de simplement calculer la référence à l'objet donné.

Le root du navigateur est un objet window, tandis que le root dans NodeJS est un global.

Explication détaillée de la gestion de la mémoire en JavaScript

L'algorithme marque les objets inaccessibles comme des déchets, puis les analyse (les collecte). L'objet racine ne sera jamais collecté.

De cette façon, les dépendances circulaires ne sont plus un problème. Dans l'exemple précédent, ni l'objet dad ni l'objet son ne sont accessibles depuis la racine. Ils seront donc tous marqués comme déchets et collectés.

Cet algorithme est implémenté dans tous les navigateurs modernes depuis 2012. Seules les performances et la mise en œuvre ont été améliorées, mais l'idée centrale de l'algorithme reste la même.

Marques déposées

Le garbage collection automatique nous permet de nous concentrer sur la création d'applications au lieu de perdre du temps sur la gestion de la mémoire. Cependant, il existe des compromis.

Utilisation de la mémoire

Étant donné que l'algorithme ne sait pas exactement quand la mémoire n'est plus nécessaire, une application JS peut utiliser plus de mémoire qu'elle n'en a réellement besoin.

Même si un objet est marqué comme poubelle, c'est au garbage collector de décider quand et si la mémoire allouée sera collectée.

如果你希望应用程序尽可能提高内存效率,那么最好使用低级语言。 但是请记住,这需要权衡取舍。

性能

收集垃圾的算法通常会定期运行以清理未使用的对象。

问题是我们开发人员不知道何时会回收。 收集大量垃圾或频繁收集垃圾可能会影响性能。然而,用户或开发人员通常不会注意到这种影响。

内存泄漏

在全局变量中存储数据,最常见内存问题可能是内存泄漏

在浏览器的 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

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer