Par rapport à C / C ++, le traitement de JavaScript en mémoire dans le JavaScript que nous utilisons nous a fait prêter plus d'attention à la rédaction de la logique commerciale en développement. Cependant, avec la complexité continue de l'entreprise, le développement d'applications à une page, les applications mobiles HTML5, les programmes Node.js, etc., le décalage et le débordement de la mémoire causés par les problèmes de mémoire en JavaScript ne sont plus inconnus.
Cet article discutera de l'utilisation de la mémoire et de l'optimisation du niveau de langue de JavaScript. Des aspects que tout le monde connaît ou un peu entendus aux choses que tout le monde ne remarquera pas la plupart du temps, nous les analyserons un par un.
1. Gestion de la mémoire au niveau de la langue
1.1 Scope
La portée est un mécanisme de fonctionnement très important dans la programmation JavaScript. Il n'attire pas l'attention des débutants dans la programmation synchrone JavaScript, mais dans la programmation asynchrone, les bonnes compétences de contrôle de la portée sont devenues une compétence nécessaire pour les développeurs JavaScript. De plus, Scope joue un rôle crucial dans la gestion de la mémoire JavaScript.
Dans JavaScript, les fonctions peuvent être appelées, avec des instructions et des lunettes globales qui peuvent être portée.
Comme indiqué dans le code suivant comme exemple:
La copie de code est la suivante:
var foo = function () {
var local = {};
};
foo ();
console.log (local); // => indéfini
var bar = function () {
local = {};
};
bar();
console.log (local); // => {}
Ici, nous définissons la fonction foo () et la fonction bar (). Leur intention est de définir une variable nommée locale. Mais le résultat final est complètement différent.
Dans la fonction foo (), nous utilisons l'instruction VAR pour déclarer qu'une variable locale est définie. Parce qu'une portée est formée à l'intérieur du corps de fonction, cette variable est définie dans la portée. De plus, il n'y a pas de traitement d'extension de portée dans la fonction foo (), donc après l'exécution de la fonction, la variable locale est également détruite. Cependant, cette variable ne peut pas être accessible dans la portée extérieure.
Dans la fonction BAR (), la variable locale n'est pas déclarée à l'aide d'une instruction VAR, mais il définit plutôt le local comme une variable globale. Par conséquent, cette variable est accessible par la portée extérieure.
La copie de code est la suivante:
local = {};
// La définition ici équivaut à
global.local = {};
1.2 Chaîne de portée
Dans la programmation JavaScript, vous rencontrerez certainement des scénarios de nidification de fonction multicouches, qui est une représentation typique des chaînes de portée.
Comme indiqué dans le code suivant:
La copie de code est la suivante:
fonction foo () {
var val = 'bonjour';
Function Bar () {
fonction baz () {
global.val = 'world;'
}
baz ();
Console.log (Val); // => Bonjour
}
bar();
}
foo ();
Sur la base de l'explication précédente sur la portée, vous pouvez penser que le résultat montré dans le code ici est le monde, mais le résultat réel est bonjour. De nombreux débutants commenceront à se sentir confus ici, alors jetons un coup d'œil sur le fonctionnement de ce code.
Depuis JavaScript, la recherche d'identifiants variables commence à partir de la portée actuelle et regarde vers l'extérieur jusqu'à la portée globale. Par conséquent, l'accès aux variables dans le code JavaScript ne peut être effectué qu'à l'extérieur, mais pas à l'envers.
L'exécution de la fonction baz () définit une VAL variable globale dans la portée globale. Dans la fonction BAR (), lors de l'accès à l'identifiant Val, le principe de recherche de l'intérieur vers l'extérieur est: s'il ne se trouve pas dans la portée de la fonction de la barre, il va à la couche précédente, c'est-à-dire la portée de la fonction foo ().
Cependant, la clé pour confondre tout le monde est: cet accès à l'identifiant trouve une variable correspondante dans la portée de la fonction foo () et ne continuera pas à regarder vers l'extérieur, de sorte que la variable globale VAL définie dans la fonction baz () n'a aucun effet dans cet accès variable.
1.3 Clôture
Nous savons que la recherche d'identifiant en JavaScript suit le principe de l'intérieur. Cependant, avec la complexité de la logique des affaires, une seule commande de livraison est loin de répondre aux nouveaux besoins croissants.
Jetons un coup d'œil au code suivant:
La copie de code est la suivante:
fonction foo () {
var local = 'bonjour';
return function () {
Retour local;
};
}
var bar = foo ();
console.log (bar ()); // => Bonjour
La technologie qui permet à la portée extérieure d'accéder à la portée intérieure comme indiqué ici est la fermeture. Grâce à l'application des fonctions d'ordre supérieur, la portée de la fonction foo () est "étendue".
La fonction foo () renvoie une fonction anonyme, qui existe dans le cadre de la fonction foo (), vous pouvez donc accéder à la variable locale dans le cadre de la fonction foo () et enregistrer sa référence. Étant donné que cette fonction renvoie directement la variable locale, la fonction BAR () peut être directement exécutée dans la portée extérieure pour obtenir la variable locale.
Les fermetures sont une caractéristique de haut niveau de JavaScript, et nous pouvons les utiliser pour obtenir des effets de plus en plus complexes pour répondre à différents besoins. Cependant, il convient de noter que parce que la fonction avec une référence de variable interne est tirée de la fonction, les variables de cette étendue ne seront pas détruites après l'annulation de la fonction jusqu'à ce que toutes les références aux variables internes soient annulées. Par conséquent, l'application des fermetures peut facilement rendre la mémoire non libre.
2. Mécanisme de recyclage de la mémoire de JavaScript
Ici, je prendrai le moteur V8 utilisé par Chrome et Node.js et lancé par Google comme exemple pour introduire 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 Park Ling "En-profondeur et facile à comprendre Node.js" pour l'apprentissage, qui est une introduction très détaillée dans le chapitre "Contrôle de la mémoire".
En V8, tous les objets JavaScript sont alloués via le "tas".
Lorsque nous déclarons et attribuons des valeurs dans le code, V8 alloue une partie de cette variable dans la mémoire du tas. Si la mémoire demandée est insuffisante pour stocker cette variable, V8 continuera de demander la mémoire jusqu'à ce que la taille du tas atteigne la limite de mémoire de V8. Par défaut, la limite supérieure de la mémoire de tas de V8 est de 1464 Mo sur des systèmes 64 bits et 732 Mo sur des systèmes 32 bits, soit environ 1,4 Go et 0,7 Go.
De plus, V8 gère les objets JavaScript dans la mémoire du tas en générations: la nouvelle génération et l'ancienne génération. La nouvelle génération est des objets JavaScript avec des cycles de survie courts, tels que des variables temporaires, des chaînes, etc.; Alors que l'ancienne génération est des objets avec de longs cycles de survie après plusieurs collections de déchets, telles que les contrôleurs principaux, les objets serveur, etc.
Les algorithmes de recyclage des ordures ont toujours été une partie importante du développement des langages de programmation, et les algorithmes de recyclage des ordures utilisés dans le V8 sont principalement comme suit:
1. Algorithme Scavange: la gestion de l'espace mémoire est effectuée par la copie, principalement utilisée dans l'espace mémoire de la nouvelle génération;
2. Algorithme de marque-sweep et algorithme de compact Mark: la mémoire du tas est triée et recyclée par le marquage, principalement utilisé pour l'inspection et le recyclage des objets de vieille génération.
PS: Les implémentations plus détaillées de la collection de déchets V8 peuvent être apprises en lisant des livres, des documents et un code source connexes.
Jetons un coup d'œil sur les circonstances que le moteur JavaScript recyclera quels objets.
2.1 Portée et référence
Les débutants croient souvent à tort que lorsque la fonction est exécutée, l'objet déclaré à l'intérieur de la fonction sera détruit. Mais en fait, cette compréhension n'est pas rigoureuse et complète, et il est facile d'être confus par lui.
La référence est un mécanisme très important dans la programmation JavaScript, mais étrangement, la plupart des développeurs ne y feront pas attention ni même le comprendront. La référence fait référence à la relation abstraite "Accès au code aux objets". Il est quelque peu similaire aux pointeurs C / C ++, mais pas la même chose. La référence est également le mécanisme le plus critique du moteur JavaScript dans la collecte des ordures.
Le code suivant est un exemple:
La copie de code est la suivante:
// ......
var val = 'Hello World';
fonction foo () {
return function () {
Retour Val;
};
}
global.bar = foo ();
// ......
Après avoir lu ce code, pouvez-vous dire quels objets survivent encore une fois que cette partie du code est exécutée?
Selon les principes pertinents, les objets qui ne sont pas recyclés et libérés dans ce code incluent Val et Bar (). Qu'est-ce qui les rend exactement incapables d'être recyclés?
Comment le moteur JavaScript effectue-t-il une collection d'ordures? L'algorithme de collecte des ordures mentionné ci-dessus n'est utilisé que pendant le 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 notez simplement un nom de variable en une seule ligne sans rien faire, le moteur JavaScript pense qu'il s'agit d'un comportement d'accès à l'objet et il y a une référence à l'objet. Afin de s'assurer que le comportement de collecte des ordures n'affecte pas le fonctionnement de la logique du programme, le moteur JavaScript ne doit pas recycler les objets utilisés, sinon il sera désordonné. Par conséquent, la norme pour juger si un objet est utilisé est de savoir s'il existe toujours une référence à l'objet. Mais en fait, il s'agit d'un compromis, car les références JavaScript peuvent être transférées, il peut donc y avoir des références apportées à la portée mondiale, mais en fait, il n'est plus nécessaire d'y accéder dans la logique commerciale et devrait être recyclé, mais le moteur JavaScript croire rigidement que le programme en a toujours besoin.
Comment utiliser les variables et les références dans la posture correcte est la clé pour optimiser JavaScript du niveau de la langue.
3. Optimiser votre javascript
Enfin arrivé au point. Merci beaucoup d'avoir vu cela avec patience. Après tant de présentations ci-dessus, je crois que vous avez une bonne compréhension du mécanisme de gestion de la mémoire de JavaScript. Ensuite, les compétences suivantes vous feront vous sentir mieux.
3.1 Faire un bon usage des fonctions
Si vous avez l'habitude de lire d'excellents projets JavaScript, vous constaterez que lors du développement du code JavaScript frontal, de nombreux gros gars utilisent souvent une fonction anonyme pour l'envelopper sur la couche la plus externe du code.
La copie de code est la suivante:
(fonction() {
// Code commercial principal
}) ();
Certains sont encore plus avancés:
La copie de code est la suivante:
; (fonction (win, doc, $, indéfini) {
// Code commercial principal
}) (fenêtre, document, jQuery);
Même des solutions de chargement modulaire frontal telles que les exigences, les SeaJS, les OZJ, etc. adoptent tous une forme similaire:
La copie de code est la suivante:
// requirejs
définir (['jQuery'], fonction ($) {
// Code commercial principal
});
// Seajs
définir ('module', ['dep', 'souligner'], fonction ($, _) {
// Code commercial principal
});
Si vous dites que de nombreux codes des projets open source Node.js ne sont pas traités de cette manière, vous vous trompez. Avant d'exécuter le code, Node.js enveloppera chaque fichier .js dans le formulaire suivant:
La copie de code est la suivante:
(fonction (exportations, require, module, __dirname, __FileName) {
// Code commercial principal
});
Quels sont les avantages de faire cela? Nous savons tous qu'au début de l'article, nous avons dit que JavaScript peut avoir des fonctions dans les énoncés, avec des déclarations et une portée globale. Nous savons également que les objets définis dans la portée globale peuvent survivre jusqu'à la sortie du processus. S'il s'agit d'un grand objet, il sera gênant. Par exemple, certaines personnes aiment rendre des modèles en JavaScript:
La copie de code est la suivante:
<? Php
$ db = mysqli_connect (serveur, utilisateur, mot de passe, 'myApp');
$ sujets = mysqli_query ($ db, "select * from sujets;");
?>
<! doctype html>
<html lang = "en">
<adal>
<meta charset = "utf-8">
<Titre> Êtes-vous un gars drôle invité par les singes? </TITAL>
</ head>
<body>
<ul id = "sujets"> </ul>
<script type = "text / tmpl" id = "topic-tmpl">
<li>
<h1> <% = title%> </h1>
<p> <% = contenu%> </p>
</li>
</cript>
<script type = "text / javascript">
var data = <? php echo json_encode ($ sujets); ?>;
var topictmpl = document.QuerySelector ('# topic-tmpl'). innerHTML;
var render = fonction (tmlp, vue) {
var compilé = TMLP
.replace (// n / g, '// n')
.replace (/ <% = ([/ s / s] +?)%> / g, fonction (match, code) {
return '"+ Escape (' + code + ') +"';
});
compilé = [
'var res = "";',
'avec (vue || {}) {',
'res = "' + compilé + '";',
'}',
'return res;'
] .join ('/ n');
var fn = nouvelle fonction ('vue', compilé);
retour fn (vue);
};
var sujets = document.QuerySelector ('# sujets');
fonction init ()
data.ForEach (fonction (thème) {
sujets.innerhtml + = render (topictMpl, thème);
});
}
init ();
</cript>
</docy>
</html>
Ce type de code peut souvent être vu dans les œuvres de novices. Quels sont les problèmes ici? Si la quantité de données obtenue à partir de la base de données est très grande, la variable de données sera inactive une fois que le frontal a terminé le rendu du modèle. Cependant, comme cette variable est définie dans la portée globale, le moteur JavaScript ne le recyclera pas et ne le détruira pas. Cela continuera d'exister dans la mémoire de tas de l'ancienne génération jusqu'à la fermeture de la page.
Mais si nous apportons des modifications très simples et enroulons une couche de fonctions en dehors du code logique, l'effet sera très différent. Une fois le rendu de l'interface utilisateur terminé, la référence du code aux données est également annulée. Lorsque la fonction la plus externe est exécutée, le moteur JavaScript commence à vérifier les objets dedans et les données peuvent être recyclées.
3.2 Ne définissez jamais les variables globales
Nous avons juste parlé de cela lorsqu'une variable est définie dans la portée globale, le moteur JavaScript ne le recyclera pas et ne le détruira pas par défaut. Cela continuera d'exister dans la mémoire de tas de l'ancienne génération jusqu'à la fermeture de la page.
Ensuite, nous avons toujours suivi un principe: n'utilisez jamais de variables globales. Bien que les variables globales soient en effet très faciles à développer, les problèmes causés par les variables mondiales sont beaucoup plus graves que la commodité qu'elle apporte.
Rendre les variables moins susceptibles d'être recyclées;
1. La confusion est facilement causée lorsque plusieurs personnes collaborent;
2. Il est facile d'être interféré dans la chaîne de portée.
3. En conjonction avec la fonction d'emballage ci-dessus, nous pouvons également gérer les "variables globales" par le biais de fonctions d'enveloppe.
3,3 Variables de non-référence manuelles
Si une variable n'est pas nécessaire dans le code commercial, la variable peut être manuellement déséférée pour la recycler.
La copie de code est la suivante:
var data = {/ * Certains Big Data * /};
// bla bla bla
data = null;
3.4 Faire un bon usage des rappels
En plus d'utiliser les fermetures pour l'accès aux variables internes, nous pouvons également utiliser la fonction de rappel désormais très populaire pour le traitement commercial.
La copie de code est la suivante:
fonction getData (rappel) {
var data = 'Certains Big Data';
rappel (null, données);
}
getData (fonction (err, data) {
console.log (données);
Les fonctions de rappel sont une technologie du style de passage de continuation (CPS). Ce style de programmation transfère le foyer commercial de la fonction de la valeur de retour à la fonction de rappel. Et il présente de nombreux avantages sur les fermetures:
1. Si les paramètres passés sont le type de base (tels que les chaînes, les valeurs numériques), les paramètres formels passés dans la fonction de rappel seront des valeurs copiées, et il sera plus facile d'être recyclé après l'utilisation du code commercial;
2. Grâce à des rappels, en plus de répondre aux demandes synchrones, nous pouvons également les utiliser dans la programmation asynchrone, qui est maintenant un style d'écriture très populaire;
3. La fonction de rappel elle-même est généralement une fonction anonyme temporaire. Une fois la fonction de demande exécutée, la référence à la fonction de rappel elle-même sera annulée et elle sera recyclée.
3.5 Bonne gestion de fermeture
Lorsque notre entreprise a besoin (comme la liaison des événements circulaires, les attributs privés, les rappels avec des arguments, etc.) doivent utiliser les fermetures, veuillez faire attention aux détails.
Les événements de liaison en boucle peuvent être considérés comme un cours obligatoire pour commencer avec les fermetures JavaScript. Supposons un scénario: il y a six boutons, correspondant à six événements. Lorsque l'utilisateur clique sur le bouton, les événements correspondants sont sortis à l'endroit spécifié.
La copie de code est la suivante:
var btns = document.QuerySelectorall ('. Btn'); // 6 éléments
var output = document.QuerySelector ('# output');
Événements VAR = [1, 2, 3, 4, 5, 6];
// Cas 1
pour (var i = 0; i <btns.length; i ++) {
btns [i] .onclick = fonction (evt) {
output.innerText + = 'Clicked' + Events [i];
};
}
// Cas 2
pour (var i = 0; i <btns.length; i ++) {
btns [i] .onclick = (fonction (index) {
fonction de retour (evt) {
output.innerText + = 'Clicked' + Events [index];
};
})(je);
}
// Cas 3
pour (var i = 0; i <btns.length; i ++) {
btns [i] .onclick = (fonction (événement) {
fonction de retour (evt) {
Output.innerText + = 'Clicked' + Event;
};
}) (événements [i]);
}
La première solution ici est évidemment une erreur d'événement de liaison en boucle typique. Je ne l'expliquerai pas en détail ici. Vous pouvez vous référer à ma réponse à un internaute en détail; La différence entre les deuxième et troisième solutions réside dans les paramètres passés dans la fermeture.
Les paramètres passés dans le deuxième schéma sont l'indice de boucle actuel, tandis que ce dernier est directement transmis dans l'objet d'événement correspondant. En fait, ce dernier convient plus à de grandes quantités d'applications de données, car dans la programmation fonctionnelle JavaScript, les paramètres passés dans les appels de fonction sont des objets de base, de sorte que les paramètres formels obtenus dans le corps de la fonction seront une valeur de copie, de sorte que cette valeur est définie comme une variable locale dans la portée du corps de la fonction. Une fois la liaison de l'événement terminée, la variable des événements peut être manuellement déréférencée pour réduire l'utilisation de la mémoire dans la portée extérieure. De plus, lorsqu'un élément est supprimé, la fonction d'écoute d'événements correspondante, l'objet d'événement et la fonction de fermeture sont également détruits et recyclés.
3.6 La mémoire n'est pas un cache
Le rôle de la mise en cache dans le développement des entreprises joue un rôle important et peut réduire le fardeau des ressources spatio-temporelles. Mais il convient de noter que vous ne devez pas utiliser facilement la mémoire comme cache. La mémoire est une chose de chaque centimètre de terrain pour tout développement de programmes. S'il ne s'agit pas d'une ressource très importante, veuillez ne pas la mettre directement en mémoire ou formuler un mécanisme d'expiration pour détruire automatiquement le cache d'expiration.
4. Vérifiez l'utilisation de la mémoire de JavaScript
Dans le développement quotidien, nous pouvons également utiliser des outils pour analyser et dépanner l'utilisation de la mémoire en JavaScript.
4.1 BLINK / WEBKIT BROWSER
Dans le navigateur Blink / WebKit (Chrome, Safari, Opera, etc.), nous pouvons utiliser l'outil Profils des outils de développement pour effectuer des vérifications de mémoire sur nos programmes.
4.2 Vérification de la mémoire dans Node.js
Dans Node.js, nous pouvons utiliser des modules Node-HeapDump et Node-Memwatch pour la vérification de la mémoire.
La copie de code est la suivante:
var heapDump = require ('heapdump');
var fs = require ('fs');
var path = require ('path');
fs.writeFileSync (path.join (__ dirname, 'app.pid'), process.pid);
// ...
La copie de code est la suivante: <span style = "font-family: Georgia, 'Times New Roman', 'Bitsstream Charter', Times, Serif; Font-Size: 14px; line-height: 1.5em;"> Après avoir introduit le nœud nœud de la mémoire du tas. </span>
Copiez le code comme suit: $ kill -usr2 (cat app.pid)
De cette façon, il y aura un fichier instantané nommé dans le format Heapdump- <Sec>. <Usec> .Hepsnapshot dans le répertoire de fichiers. Nous pouvons l'ouvrir à l'aide de l'outil Profils dans les outils de développement du navigateur et le vérifier.
5. Résumé
L'article revient bientôt. Ce partage vous montre principalement le contenu suivant:
1. JavaScript est étroitement lié à l'utilisation de la mémoire au niveau de la langue;
2. Mécanismes de gestion et de recyclage de la mémoire en JavaScript;
3. Comment utiliser la mémoire plus efficacement afin que le JavaScript produit puisse être plus élargi et énergique;
4. Comment effectuer des vérifications de mémoire lors de la rencontre des problèmes de mémoire.
J'espère qu'en apprenant cet article, vous pouvez produire un meilleur code JavaScript pour que votre mère se sente à l'aise et que votre patron se sente à l'aise.