Os manipuladores de events em um nó DOM são excluídos pelo nó?

(Nota: Eu estou usando o jQuery abaixo, mas a questão é realmente um JavaScript geral).

Digamos que eu tenha uma div#formsection cujo conteúdo é atualizado repetidamente usando o AJAX, assim:

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

Sempre que eu atualizo esse div, eu disparo em um evento personalizado , que vincula os manipuladores de events a alguns dos elementos recém-adicionados, assim:

 // 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 */}); 

Portanto, estou vinculando manipuladores de events a alguns nós DOM e, posteriormente, excluindo esses nós DOM e inserindo novos ( html() faz isso) e vinculando manipuladores de events aos novos nós DOM.

Meus manipuladores de events são excluídos juntamente com os nós DOM aos quais estão vinculados? Em outras palavras, à medida que eu carrego novas seções, há muitos manipuladores de events inúteis se acumulando na memory do navegador, aguardando events nos nós DOM que não existem mais ou eles são limpos quando seus nós DOM são excluídos?

Pergunta bônus: como posso testar isso sozinho?

As funções do manipulador de events estão sujeitas à mesma garbage collection que outras variables. Isso significa que eles serão removidos da memory quando o intérprete determinar que não há meios possíveis para obter uma referência à function. A simples exclusão de um nó, no entanto, não garante a garbage collection. Por exemplo, pegue este nó e o manipulador de events associado

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

Agora vamos remover o nó do DOM

 node.parentNode.removeChild(node); 

Então, o node não estará mais visível em seu website, mas ainda existe claramente na memory, assim como o manipulador de events

 node.onclick(); //alerts hai 

Enquanto a referência ao node ainda estiver acessível de alguma forma, suas propriedades associadas (das quais onclick é uma) permanecerão intactas.

Agora vamos tentar sem criar uma variável pendente

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

Nesse caso, parece não haver mais nenhuma maneira de acessar o #test do nó DOM, portanto, quando um ciclo de garbage collection é executado, o manipulador onclick deve ser removido da memory.

Mas este é um caso muito simples. O uso de closures pelo Javascript pode complicar muito a determinação da capacidade de garbage collection. Vamos tentar vincular uma function de manipulador de events um pouco mais complexa ao onclick

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

Então, quando você clicar em #test, o elemento será removido instantaneamente, no entanto, um segundo depois, e a cada segundo depois, você verá um número incrementado impresso em seu console. O nó é removido e nenhuma outra referência a ele é possível, mas parece que partes dele permanecem. Nesse caso, a function do manipulador de events provavelmente não é retida na memory, mas o escopo criado é.

Então a resposta que eu acho é; depende. Se houver referências pendentes e acessíveis a nós DOM excluídos, seus manipuladores de events associados ainda residirão na memory, juntamente com o restante de suas propriedades. Mesmo que esse não seja o caso, o escopo criado pelas funções do manipulador de events ainda pode estar em uso e na memory.

Na maioria dos casos (e felizmente ignorando o IE6) é melhor confiar apenas no Garbage Collector para fazer o seu trabalho, o JavaScript não é C depois de tudo. No entanto, em casos como o último exemplo, é importante escrever funções de destruição de algum tipo para encerrar implicitamente a funcionalidade.

O jQuery faz grandes esforços para evitar vazamentos de memory ao remover elementos do DOM. Contanto que você esteja usando o jQuery para excluir nós DOM, a remoção de manipuladores de events e dados extras devem ser manipulados pelo jQuery. Eu recomendaria muito ler os segredos de John Nervig de um JavaScript Ninja enquanto ele entra em grandes detalhes sobre potenciais vazamentos em diferentes navegadores e como bibliotecas JavaScript como jQuery contornam esses problemas. Se você não estiver usando o jQuery, você definitivamente precisa se preocupar em vazar memory através de manipuladores de events órfãos ao excluir nós DOM.

Você pode precisar remover esses manipuladores de events.

Vazamentos de memory Javascript após o descarregamento de uma página da web

Em nosso código, que não é baseado no jQuery, mas em algum protótipo, temos inicializadores e destruidores em nossas classs. Descobrimos que é absolutamente essencial remover manipuladores de events de objects DOM quando destruímos não apenas nosso aplicativo, mas também widgets individuais durante o tempo de execução.

Caso contrário, acabamos com vazamentos de memory no IE.

É surpreendentemente fácil obter vazamentos de memory no IE – mesmo quando descarregamos a página, devemos ter certeza de que o aplicativo “desliga” de forma limpa, arrumando tudo – ou o processo do IE crescerá com o tempo.

Edit: Para fazer isso corretamente, temos um observador de events na window para o evento unload . Quando esse evento chega, nossa cadeia de destruidores é chamada para limpar corretamente cada object.

E algum código de amostra:

 /** * @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); }, 

Não necessariamente

A documentação do método empty() do jQuery responde à minha pergunta e fornece uma solução para o meu problema. Diz:

Para evitar vazamentos de memory, o jQuery remove outras construções, como dados e manipuladores de events, dos elementos filho antes de remover os elementos.

Então: 1) se não fizéssemos isso explicitamente, teríamos vazamentos de memory e 2) usando empty() , eu posso evitar isso.

Portanto, devo fazer isso:

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

Ainda não está claro para mim se o .html() cuidaria disso sozinho, mas uma linha extra para ter certeza não me incomoda.

Eu queria me conhecer assim, depois de um pequeno teste, acho que a resposta é sim.

removeEvent é chamado quando você .remove () algo do DOM.

Se você quiser ver você mesmo, você pode tentar isso e seguir o código definindo um ponto de interrupção. (Eu estava usando o jquery 1.8.1)

Adicione um novo div primeiro:
$('body').append('

')

Verifique $.cache para se certificar de que não há events anexados a ele. (deve ser o último object)

Anexe um evento de clique a ele:
$('#test').on('click',function(e) {console.log("clicked")});

Teste e veja um novo object em $.cache :
$('#test').click()

Remova-o e você poderá ver que o object em $.cache se foi:
$('#test').remove()