Les gestionnaires d'événements sur un noeud DOM sont-ils supprimés avec le nœud?

(Remarque: J'utilise jQuery ci-dessous, mais la question est vraiment un Javascript général).

Dites que j'ai une div#formsection dont le contenu est mis à jour à plusieurs reprises à l'aide d'AJAX, comme ceci:

 var formSection = $('div#formsection'); var newContents = $.get(/* URL for next section */); formSection.html(newContents); 

Chaque fois que je met à jour cette division, je déclenche un événement personnalisé , qui lie les gestionnaires d'événements à certains des éléments nouvellement ajoutés, comme ceci:

 // When the first section of the form is loaded, this runs... formSection.find('select#phonenumber').change(function(){/* stuff */}); ... // ... when the second section of the form is loaded, this runs... formSection.find('input#foo').focus(function(){/* stuff */}); 

Donc: je lié les gestionnaires d'événements à certains noeuds DOM, puis, en supprimant ces nœuds DOM et en insérant de nouveaux ( html() fait) et les gestionnaires d'événements de liaison aux nouveaux noeuds DOM.

Les gestionnaires d'événements sont-ils supprimés avec les nœuds DOM auxquels ils sont obligés? En d'autres termes, alors que je charge de nouvelles sections, beaucoup de gestionnaires d'événements inutiles s'accumulent dans la mémoire du navigateur, en attendant des événements sur des nœuds DOM qui n'existent plus ou sont-ils effacés lorsque leurs nœuds DOM sont supprimés?

Question bonus: comment peut-on tester cela moi-même?

Les fonctions du gestionnaire d'événements sont soumises à la même collecte de déchets que d'autres variables. Cela signifie qu'ils seront supprimés de la mémoire lorsque l'interprète détermine qu'il n'existe aucun moyen possible d'obtenir une référence à la fonction. Il suffit simplement de supprimer un nœud mais ne garantit pas la collecte des ordures. Par exemple, prenez ce nœud et le gestionnaire d'événements associé

 var node = document.getElementById('test'); node.onclick = function() { alert('hai') }; 

Maintenant, supprimez le nœud du DOM

 node.parentNode.removeChild(node); 

Ainsi, le node ne sera plus visible sur votre site Web, mais il existe clairement encore dans la mémoire, de même que le gestionnaire d'événements

 node.onclick(); //alerts hai 

Tant que la référence au node est toujours accessible, les propriétés associées (dont onclick est un) resteront intactes.

Maintenant, essayons sans créer une variable pendante

 document.getElementById('test').onclick = function() { alert('hai'); } document.getElementById('test').parentNode.removeChild(document.getElementById('test')); 

Dans ce cas, il ne semble pas y avoir d'autre moyen d'accéder au nœud n ° #test, alors lorsqu'un cycle de collecte des ordures est exécuté, le onclick cliché doit être retiré de la mémoire.

Mais c'est un cas très simple. L'utilisation par Javascript de fermetures peut compliquer grandement la détermination de la collecte des ordures. Essayons de lier une fonction de gestionnaire d'événements un peu plus complexe à onclick

 document.getElementById('test').onclick = function() { var i = 0; setInterval(function() { console.log(i++); }, 1000); this.parentNode.removeChild(this); }; 

Donc, lorsque vous cliquez sur #test, l'élément sera instantanément supprimé, mais une seconde plus tard, et chaque seconde après, vous verrez un nombre incrémenté imprimé sur votre console. Le nœud est supprimé, et aucune autre référence n'est possible, mais il semble que certaines parties restent. Dans ce cas, la fonction de gestionnaire d'événements elle-même n'est probablement pas retenue en mémoire mais la portée qu'elle a créée est.

Donc, je pense que la réponse est; ça dépend. S'il y a pendaison, des références accessibles aux noeuds DOM supprimés, leurs gestionnaires d'événements associés résident encore dans la mémoire, ainsi que le reste de leurs propriétés. Même si ce n'est pas le cas, la portée créée par les fonctions du gestionnaire d'événements pourrait encore être utilisée et en mémoire.

Dans la plupart des cas (et heureusement ignorer IE6), il est préférable de simplement faire confiance au Collecteur de déchets pour faire son travail, Javascript n'est pas C après tout. Cependant, dans des cas comme le dernier exemple, il est important d'écrire des fonctions destructrices de quelque sorte pour arrêter implicitement la fonctionnalité.

JQuery fait de grands efforts pour éviter les fuites de mémoire lors de la suppression des éléments du DOM. Tant que vous utilisez jQuery pour supprimer les noeuds DOM, la suppression des gestionnaires d'événements et des données supplémentaires doivent être gérées par jQuery. Je recommande vivement de lire les secrets de John Resig d'un Ninja de JavaScript, puisqu'il découvre très bien les éventuelles fuites dans différents navigateurs et comment les bibliothèques JavaScript comme jQuery contournent ces problèmes. Si vous n'utilisez pas jQuery, vous devez vous inquiéter de la perte de mémoire grâce aux gestionnaires d'événements orphelins lors de la suppression des nœuds DOM.

Vous devrez peut-être supprimer ces gestionnaires d'événements.

Fuite de mémoire Javascript après avoir déchargé une page Web

Dans notre code, qui n'est pas basé sur jQuery, mais un prototype déviant, nous avons des initialiseurs et destructeurs dans nos classes. Nous avons trouvé qu'il est absolument essentiel de supprimer les gestionnaires d'événements des objets DOM lorsque nous détruisons non seulement notre application, mais aussi des widgets individuels pendant l'exécution.

Sinon, nous finissons avec des fuites de mémoire dans IE.

Il est étonnamment facile d'obtenir des fuites de mémoire dans IE – même lorsque nous déchargons la page, nous devons être sûr que l'application "ferme" proprement, ranger tout – ou le processus IE augmentera avec le temps.

Modifier: Pour ce faire, nous avons un observateur d'événement sur la window pour l'événement de unload . Lorsque cet événement arrive, notre chaîne de destructeurs est appelée à nettoyer correctement chaque objet.

Et un certain exemple de code:

 /** * @constructs */ initialize: function () { // call superclass MyCompany.Control.prototype.initialize.apply(this, arguments); this.register(MyCompany.Events.ID_CHANGED, this.onIdChanged); this.register(MyCompany.Events.FLASHMAPSV_UPDATE, this.onFlashmapSvUpdate); }, destroy: function () { if (this.overMap) { this.overMap.destroy(); } this.unregister(MyCompany.Events.ID_CHANGED, this.onIdChanged); this.unregister(MyCompany.Events.FLASHMAPSV_UPDATE, this.onFlashmapSvUpdate); // call superclass MyCompany.Control.prototype.destroy.apply(this, arguments); }, 

Pas nécessairement

La documentation sur la méthode empty() de jQuery répond à ma question et me donne une solution à mon problème. Ça dit:

Pour éviter les fuites de mémoire, jQuery supprime d'autres constructions telles que les gestionnaires de données et d'événements à partir des éléments enfants avant de supprimer les éléments eux-mêmes.

Donc: 1) si nous ne le faisions pas explicitement, nous aurions des fuites de mémoire, et 2) en utilisant empty() , je peux éviter cela.

Par conséquent, je devrais faire ceci:

 formSection.empty(); formSection.html(newContents); 

Il n'est toujours pas clair pour moi si .html() prendrait soin de cela par lui-même, mais une ligne supplémentaire pour être sûr ne me dérange pas.

Je voulais me connaître après un petit test, je pense que la réponse est oui.

RemoveEvent est appelé lorsque vous .remove () quelque chose du DOM.

Si vous voulez le voir vous-même, vous pouvez essayer ceci et suivre le code en définissant un point d'arrêt. (J'utilisais jquery 1.8.1)

Ajoutez un nouveau div premier:
$('body').append('<div id="test"></div>')

Vérifiez $.cache pour vous assurer qu'il n'y a aucun événement qui s'y rattache. (Il devrait être le dernier objet)

Joignez-lui un événement de clic:
$('#test').on('click',function(e) {console.log("clicked")});

Testez-le et voyez un nouvel objet en $.cache :
$('#test').click()

Supprimez-le et vous pouvez voir l'objet en $.cache disparu aussi:
$('#test').remove()