Quels sont certains des problèmes standard ou des modèles de codage dans jQuery qui entraînent des fuites de mémoire?
J'ai vu un certain nombre de questions liées à la suppression de ajax () call ou jsonp ou DOM sur StackOverflow. La plupart des questions sur les fuites de mémoire jQuery sont axées sur des problèmes ou des navigateurs spécifiques et il serait bien d'avoir une liste des modèles de fuite de mémoire standard dans jQuery.
Voici quelques questions connexes sur SO:
Ressources sur le web:
D'après ce que je comprends, la gestion de la mémoire en javascript se fait par comptage de références - bien qu'une référence à un objet existe toujours, elle ne sera pas désallouée. Cela signifie que la création d'une fuite de mémoire dans une application d'une seule page est triviale et peut déclencher celles qui proviennent d'un arrière-plan Java. Ce n'est pas spécifique à JQuery. Prenez le code suivant par exemple :
function MyObject = function(){
var _this = this;
this.count = 0;
this.getAndIncrement = function(){
_this.count++;
return _this.count;
}
}
for(var i = 0; i < 10000; i++){
var obj = new MyObject();
obj.getAndIncrement();
}
Il semblera normal jusqu'à ce que vous regardiez l'utilisation de la mémoire. Les instances de MyObject ne sont jamais désallouées lorsque la page est active, en raison du pointeur "_this" (augmentez la valeur maximale de i pour la voir de façon plus spectaculaire.). (Dans les anciennes versions de IE, elles n'étaient jamais désallouées jusqu'à la fin du programme.) Étant donné que les objets javascript peuvent être partagés entre les cadres (je ne recommande pas d'essayer cela car c'est très capricieux.), sont des cas où même dans un navigateur moderne, les objets javascript peuvent traîner beaucoup plus longtemps qu'ils ne le devraient.
Dans le contexte de jquery, les références sont souvent stockées pour économiser les frais généraux de la recherche dom - par exemple:
function run(){
var domObjects = $(".myClass");
domObjects.click(function(){
domObjects.addClass(".myOtherClass");
});
}
Ce code conservera indéfiniment domObject (et tout son contenu), en raison de sa référence dans la fonction de rappel.
Si les rédacteurs de jquery ont manqué des instances comme celle-ci en interne, alors la bibliothèque elle-même fuira, mais le plus souvent c'est le code client.
Le deuxième exemple peut être corrigé en effaçant explicitement le pointeur lorsqu'il n'est plus requis:
function run(){
var domObjects = $(".myClass");
domObjects.click(function(){
if(domObjects){
domObjects.addClass(".myOtherClass");
domObjects = null;
}
});
}
ou refaire la recherche:
function run(){
$(".myClass").click(function(){
$(".myClass").addClass(".myOtherClass");
});
}
Une bonne règle de base est de faire attention lorsque vous définissez vos fonctions de rappel et d'éviter trop d'imbrication dans la mesure du possible.
Edit: Comme cela a été souligné dans les commentaires d'Erik, vous pouvez également utiliser le pointeur this pour éviter la recherche inutile de dom:
function run(){
$(".myClass").click(function(){
$(this).addClass(".myOtherClass");
});
}
Je vais contribuer un anti-modèle ici, qui est la fuite de "référence de chaîne intermédiaire".
L'une des forces de jQuery est son API de chaînage, qui vous permet de continuer à modifier, filtrer et manipuler les éléments:
$(".message").addClass("unread").find(".author").addClass("noob");
À la fin de cette chaîne, vous avez un objet jQuery avec tous les éléments ".message .author", mais que objet fait référence à et objet avec les éléments ".message" d'origine. Vous pouvez y accéder via la méthode .end()
et leur faire quelque chose:
$(".message")
.find(".author")
.addClass("prolific")
.end()
.addClass("unread");
Maintenant, lorsqu'il est utilisé de cette façon, il n'y a aucun problème de fuite. Cependant, si vous affectez le résultat d'une chaîne à une variable qui a une longue durée de vie, les références arrières aux ensembles précédents restent et ne peuvent pas être récupérées tant que la variable n'est pas hors de portée. Si cette variable est globale, les références ne sortent jamais du domaine.
Ainsi, par exemple, disons que vous avez lu sur un article de blog de 2008 que $("a").find("b")
était "plus efficace" que $("a b")
(même si ce n'est pas digne de penser à une telle micro-optimisation) . Vous décidez que vous avez besoin d'un global à l'échelle de la page pour contenir une liste d'auteurs, alors procédez comme suit:
authors = $(".message").find(".author");
Vous avez maintenant un objet jQuery avec la liste des auteurs, mais il fait également référence à un objet jQuery qui est la liste complète des messages. Vous ne l'utiliserez probablement jamais ou vous ne saurez même pas qu'il existe, et cela prend de la mémoire.
Notez que les fuites ne peuvent se produire qu'avec les méthodes qui sélectionnent les éléments new d'un ensemble existant, tels que .find
, .filter
, .children
Etc. les documents indiquent quand un nouvea ensemble est retourné. La simple utilisation d'une API de chaînage ne provoque pas de fuite si la chaîne a des méthodes simples sans filtrage comme .css
, Donc c'est correct:
authors = $(".message .author").addClass("prolific");