Par rapport au C/C, le traitement de la mémoire de JavaScript que nous utilisons nous a permis d'accorder plus d'attention à l'écriture de la logique métier lors du développement. Cependant, avec la complexité continue des affaires et le développement d'applications monopages, d'applications mobiles HTML5, de programmes Node.js, etc., des phénomènes tels que le retard et le débordement de mémoire provoqués par des problèmes de mémoire en JavaScript ne sont plus inconnus.
Cet article discutera de l'utilisation et de l'optimisation de la mémoire au niveau du langage JavaScript. Des aspects que tout le monde connaît ou dont on a un peu entendu parler, aux domaines que la plupart des gens ne remarquent pas, nous les analyserons un par un.
1. Gestion de la mémoire au niveau du langage
1.1 Portée
La portée est un mécanisme de fonctionnement très important dans la programmation JavaScript. Dans la programmation JavaScript synchrone, elle n'attire pas pleinement l'attention des débutants, mais dans la programmation asynchrone, de bonnes compétences en contrôle de portée deviennent la clé du développement JavaScript. De plus, la portée joue un rôle crucial dans la gestion de la mémoire JavaScript.
En JavaScript, les portées peuvent être formées par des appels de fonction, avec des instructions et une portée globale.
Prenons comme exemple le code suivant :
Dans la fonction foo(), nous utilisons l'instruction var pour déclarer et définir une variable locale puisqu'une portée sera formée à l'intérieur du corps de la fonction, cette variable est définie dans la portée. De plus, le corps de la fonction foo() n'effectue aucun traitement d'extension de portée, donc une fois la fonction exécutée, la variable locale est également détruite. La variable n'est pas accessible dans la portée externe.
Dans la fonction bar(), la variable locale n'est pas déclarée à l'aide de l'instruction var. Au lieu de cela, local est défini directement comme une variable globale. Par conséquent, la portée externe peut accéder à cette variable.
Car en JavaScript, la recherche des identifiants de variables commence à partir de la portée actuelle et recherche vers l'extérieur jusqu'à la portée globale. Par conséquent, l’accès aux variables dans le code JavaScript ne peut se faire que vers l’extérieur, et non l’inverse.
L'exécution de la fonction baz() définit une variable globale val dans la portée globale. Dans la fonction bar(), lors de l'accès à l'identifiant val, le principe de recherche est de l'intérieur vers l'extérieur : s'il n'est pas trouvé dans le périmètre de la fonction bar, il passe au niveau supérieur, c'est-à-dire dans le périmètre de foo () fonction Rechercher dans la portée.
1.3 Fermeture
Nous savons que la recherche d'identifiant en JavaScript suit le principe de l'envers. Cependant, compte tenu de la complexité de la logique métier, une seule séquence de livraison est loin de répondre aux nouveaux besoins croissants.Regardons d'abord le code suivant :
La technologie présentée ici pour permettre à la portée extérieure d'accéder à la portée intérieure est la fermeture (Closure). Grâce à l'application de fonctions d'ordre supérieur, la portée de la fonction foo() a été "étendue".
La fonction foo() renvoie une fonction anonyme, qui existe dans la portée de la fonction foo(), vous pouvez donc accéder à la variable locale dans la portée de la fonction foo() et enregistrer sa référence. Puisque cette fonction renvoie directement la variable locale, la fonction bar() peut être directement exécutée dans la portée externe pour obtenir la variable locale.
La fermeture est une fonctionnalité avancée de JavaScript. Nous pouvons l'utiliser pour obtenir des effets plus complexes afin de répondre à différents besoins. Cependant, il convient de noter que, étant donné que la fonction avec des références de variables internes est retirée de la fonction, les variables de la portée ne seront pas nécessairement détruites après l'exécution de la fonction jusqu'à ce que toutes les références aux variables internes soient libérées. Par conséquent, l’application de fermetures peut facilement empêcher la libération de la mémoire.
2. Mécanisme de recyclage de la mémoire JavaScript
Ici, je vais prendre comme exemple le moteur V8 lancé par Google utilisé par Chrome et Node.js pour présenter brièvement le mécanisme de recyclage de la mémoire de JavaScript. Pour un contenu plus détaillé, vous pouvez acheter le livre de mon bon ami Pu Ling "Speaking in". Langage simple et profond "Node.js" à apprendre, dans lequel le chapitre "Contrôle de la mémoire" a une introduction assez détaillée.
Dans la V8, tous les objets JavaScript se voient allouer de la mémoire via le « tas ».
Lorsque nous déclarons une variable dans le code et attribuons une valeur, V8 allouera une partie de la mémoire tas à la variable. Si la mémoire allouée n'est pas suffisante pour stocker cette variable, V8 continuera à demander de la mémoire jusqu'à ce que la taille du tas atteigne la limite de mémoire supérieure de V8. Par défaut, la limite supérieure de la taille de la mémoire tas du V8 est de 1 464 Mo dans les systèmes 64 bits et de 732 Mo dans les systèmes 32 bits, soit environ 1,4 Go et 0,7 Go.
De plus, la V8 gère les objets JavaScript en mémoire tas par génération : nouvelle génération et ancienne génération. La nouvelle génération fait référence aux objets JavaScript avec un cycle de vie court, comme les variables temporaires, les chaînes, etc. tandis que l'ancienne génération fait référence aux objets qui ont survécu à plusieurs garbage collection et ont un long cycle de vie, comme les contrôleurs principaux, les objets serveur. , etc.
Les algorithmes de garbage collection ont toujours été une partie importante du développement des langages de programmation, et les algorithmes de garbage collection utilisés dans la V8 incluent principalement les éléments suivants :
1.Algorithme Scavange : gestion de l'espace mémoire par copie, principalement utilisé pour l'espace mémoire de la nouvelle génération
2.Algorithme Mark-Sweep et algorithme Mark-Compact : organiser et organiser la mémoire du tas grâce au marquage Le recyclage est ; principalement utilisé pour l'inspection et le recyclage des objets d'ancienne génération.
PS : Une mise en œuvre plus détaillée du garbage collection V8 peut être apprise en lisant des livres, des documents et le code source pertinents.
Voyons quels objets le moteur JavaScript recyclera et dans quelles circonstances.
2.1 Portée et référence
Les débutants croient souvent à tort que lorsque l'exécution de la fonction est terminée, l'objet déclaré à l'intérieur de la fonction sera détruit. Mais en réalité, cette compréhension n’est ni rigoureuse ni exhaustive, et elle peut facilement prêter à confusion.
La référence est un mécanisme très important dans la programmation JavaScript, mais ce qui est étrange, c'est que la plupart des développeurs n'y prêtent pas attention ni même ne la comprennent. La référence fait référence à la relation abstraite de « l'accès du code aux objets ». Elle est quelque peu similaire aux pointeurs C/C, mais ce n'est pas la même chose. Les références constituent également le mécanisme le plus critique pour le garbage collection par le moteur JavaScript.
Prenons comme exemple le code suivant :
Après avoir lu ce code, pouvez-vous dire quels objets sont encore vivants après l'exécution de cette partie du code ?
Selon les principes pertinents, les objets qui n'ont pas été recyclés et libérés dans ce code incluent val et bar(). Quelle est la raison pour laquelle ils ne peuvent pas être recyclés ?
Comment le moteur JavaScript effectue-t-il le garbage collection ? L'algorithme de collecte des déchets mentionné précédemment n'est utilisé que lors du recyclage, alors comment sait-il quels objets peuvent être recyclés et quels objets doivent continuer à survivre ? La réponse est une référence à un objet JavaScript.
Dans le code JavaScript, même si vous écrivez simplement un nom de variable sur une ligne séparée sans effectuer aucune opération, le moteur JavaScript pensera qu'il s'agit d'un comportement d'accès à l'objet, et qu'il existe une référence à l'objet. Afin de garantir que le comportement du garbage collection n'affecte pas le fonctionnement de la logique du programme, le moteur JavaScript ne doit pas recycler les objets utilisés, sinon ce sera chaotique. Ainsi, le critère pour juger si un objet est utilisé est de savoir s’il existe encore une référence à l’objet. Mais en fait, c'est un compromis, car les références JavaScript peuvent être transférées, alors certaines références peuvent être amenées à la portée globale, mais en fait il n'est pas nécessaire de les modifier dans la logique métier. Une fois accédées, elles doivent être recyclées, mais le moteur JavaScript continuera de croire fermement que le programme en a toujours besoin.
Comment utiliser correctement les variables et les références est la clé pour optimiser JavaScript au niveau du langage.
3. Optimisez votre JavaScript
Enfin, j'arrive au point. Merci beaucoup pour votre patience en lisant ceci. Après toutes les introductions ci-dessus, je pense que vous avez déjà une bonne compréhension du mécanisme de gestion de la mémoire de JavaScript. Ensuite, les techniques suivantes vous rendront encore plus puissant. .
3.1 Faire bon usage des fonctions
Si vous avez l'habitude de lire d'excellents projets JavaScript, vous constaterez que lorsque de nombreux experts développent du code JavaScript frontal, ils utilisent souvent une fonction anonyme pour envelopper la couche la plus externe du code.
Quels sont les avantages de faire cela ? Nous savons tous que, comme mentionné au début de l'article, les portées en JavaScript incluent des appels de fonction, avec des instructions et une portée globale. Et nous savons également que les objets définis dans la portée globale sont susceptibles de survivre jusqu'à la fin du processus. S'il s'agit d'un objet volumineux, cela sera gênant. Par exemple, certaines personnes aiment faire du rendu de modèles en JavaScript :
这种代码在新手的作品中经常能看得到,这里存在什么问题呢?如果在从数据库中获取到Les données relatives aux données sont Il s'agit d'une application Javascript.就会一直存在于老生代堆内存中,直到页面被关闭。
可是如果我们作出一些很简单的修改,在逻辑代码外包装一层函数,这样效果就大不同了。 UI渲染完成之后,代码对data的引用也就随之解除,在最外层函数执行完毕时,JavaScript引擎就开始对其中的对象进行检查,data也就可以随之被回收。
3.2 绝对不要定义全局变量
我们刚才也谈到了,当一个变量被定义在全局作用域中,默认情况下JavaScript et Javascript 。如此该变量就会一直存在于老生代堆内存中,直到页面被关闭。
那么我们就一直遵循一个原则:绝对不要使用全局变量。虽然全局变量在开发中确实很省事,但是全局变量所导致的问题远比其所带来的方便更严重。
使变量不易被回收;
1.多人协作时容易产生混淆;
2.在作用域链中容易被干扰。
3.配合上面的包装函数,我们也可以通过包装函数来处理『全局变量』。
3.3 手工解除变量引用
如果在业务代码中,一个变量已经确切是不再需要了,那么就可以手工解除变量引用,以使其被回收。
3.4 善用回调
除了使用闭包进行内部变量访问,我们还可以使用现在十分流行的回调函数来进行业务处理。
La fonction de rappel est une technologie de style de passage de continuation (CPS). Ce style de programmation déplace l'orientation commerciale de la fonction de la valeur de retour vers la fonction de rappel. Et il présente de nombreux avantages par rapport aux fermetures :
1. Si les paramètres transmis sont des types de base (tels que des chaînes, des valeurs), les paramètres formels transmis dans la fonction de rappel seront des valeurs copiées et le code métier sera plus facilement recyclé après utilisation
2 ; .Grâce aux rappels, en plus de compléter des requêtes synchrones, nous pouvons également l'utiliser dans la programmation asynchrone, qui est un style d'écriture très populaire actuellement
3. La fonction de rappel elle-même est généralement une fonction anonyme temporaire une fois demandée après la fonction. est exécuté, la référence de la fonction de rappel elle-même sera libérée et elle-même sera recyclée.
3.5 Bonne gestion de la fermeture
Lorsque nos exigences commerciales (telles que la liaison d'événements de boucle, les propriétés privées, les rappels contenant des paramètres, etc.) doivent utiliser des fermetures, veuillez faire attention aux détails.
Les événements de liaison de boucle peuvent être considérés comme un cours obligatoire pour démarrer avec les fermetures JavaScript. Nous supposons un scénario : il existe six boutons, correspondant à six types d'événements. Lorsque l'utilisateur clique sur le bouton, l'événement correspondant est généré. à l'endroit indiqué.
La première solution ici est évidemment une erreur typique d'événement de liaison de boucle. Je n'entrerai pas dans les détails ici. Pour plus de détails, vous pouvez vous référer à la réponse que j'ai donnée à un internaute et la différence entre la deuxième et la troisième solution réside. dans les paramètres de fermeture entrants.
Le paramètre transmis dans la deuxième solution est l'indice de la boucle actuelle, tandis que ce dernier passe directement l'objet événement correspondant. En fait, ce dernier est plus adapté aux applications de données à grande échelle, car dans la programmation fonctionnelle JavaScript, les paramètres transmis lors de l'appel de la fonction sont des objets de type de base, donc les paramètres formels obtenus dans le corps de la fonction seront une valeur copiée, doncCette valeur est définie comme une variable locale dans la portée du corps de la fonction.Une fois la liaison d'événement terminée, la variable d'événements peut être déréférencée manuellement pour réduire l'utilisation de la mémoire dans la portée externe. Et lorsqu'un élément est supprimé, la fonction d'écoute d'événement, l'objet d'événement et la fonction de fermeture correspondants sont également détruits et recyclés.
3.6 La mémoire n'est pas un cache
La mise en cache joue un rôle important dans le développement commercial et peut réduire la charge de ressources en temps et en espace. Mais il convient de noter qu’il n’est pas facile d’utiliser la mémoire comme cache. La mémoire est une ressource précieuse pour tout développement de programme. Si ce n'est pas une ressource très importante, veuillez ne pas la placer directement dans la mémoire ou développer un mécanisme d'expiration pour détruire automatiquement le cache expiré.
4. Vérifiez l'utilisation de la mémoire JavaScript
Dans le développement quotidien, nous pouvons également utiliser certains outils pour analyser et dépanner l'utilisation de la mémoire en JavaScript.
4.1 Navigateur Blink / Webkit
Dans les navigateurs Blink/Webkit (Chrome, Safari, Opera etc.), nous pouvons utiliser l'outil Profils de Developer Tools pour vérifier la mémoire de notre programme.
4.2 Vérification de la mémoire dans Node.js
Dans Node.js, nous pouvons utiliser les modules node-heapdump et node-memwatch pour la vérification de la mémoire.
In this way, there will be a snapshot file named in the format of heapdump-
5. Summary
The end of the article soon came. This sharing mainly shows you the following points:
1. JavaScript is closely related to memory usage at the language level;
2. Memory management and recycling mechanism in JavaScript;
3. How to use memory more efficiently so that the produced JavaScript Can have more vitality for expansion;
4. How to perform memory check when encountering memory problems.
I hope that by studying this article, you can produce better JavaScript code, which will reassure your mother and your boss.