Javascript - assistindo mudanças no DOM sem temporizadores

Existem basicamente duas formas de ouvir mudanças em um elemento no DOM: ou você usa eventos, ou, em casos mais extremos, temporizadores.

Como saber quando um atributo de um elemento mudou? Como saber se os filhos de um elemento foram alterados? Não tem outra forma de fazer, que não seja usando temporizadores?

Sim, tem! E é isso que eu quero mostrar nesse post! Vem comigo :D

Quando uma ação é executada em algum elemento do DOM (click, mouseenter, mousedown, etc.), é possível atribuir um evento a esse elemento, que irá disparar uma função de callback para que você possa manipular o elemento no momento certo.

Mas, e quando temos, por exemplo, uma mudança de atributo? Ou ainda, uma mudança na árvore do DOM, onde você precisa saber exatamente quando esses eventos ocorrem para tomar alguma decisão? Como fazer?

Provavelmente você logo pensa:

Temporizadores! Vou usar um setInterval() ou um setTimeout() recursivo, e, a cada x milissegundos, eu executo uma função :D

Funciona, mas não é a melhor solução. Se você tiver muitos temporizadores executando juntos, você pode acabar deixando sua aplicação lenta, e até travar o navegador do usuário!

Como eu posso então resolver isso de forma elegante?

MutationObserver

MutationObserver nos dá um caminho para reagir à mudanças no DOM. Ele foi projetado para substituir o Mutation Events, que foi definido na especificação de Eventos do DOM 3.

O MUtationObserver é um construtor, e funciona de forma parecida com o addEventListener, com a diferença que ele não é atribuído mais de uma vez por elemento, se for utilizada a mesma instância.

Com o addEventListener, se você atribuir duas vezes um listener para o evento de click, ao clicar nesse elemento, a função de callback será disparada duas vezes. Isso não acontece com o MutationObserver. Independente de quantas vezes você atribuí-lo ao mesmo elemento, o callback executará só uma vez.

Construtor

Para começarmos a observar as mudanças em um elemento, primeiro precisamos instanciar o construtor:

1
var observer = new MutationObserver( callback );

A função de callback é executada a cada mudança no DOM, e ela pode receber dois argumentos: o primeiro é um array de objetos, cada um do tipo MutationRecord. O segundo é uma instância do MutationObserer.

Métodos da instância

observe()

O observe() registra a instância do MutationObserver para receber notificações do DOM para um nó específico:

1
observer.observe( target, options );

Parâmetro target:

O target é o nó que será observado por mudanças.

O options é um objeto que especifíca as mudanças que devem ser observadas.

Por exemplo:

Se eu quiser verificar quando um nó descendente (filho) for adicionado ou removido, ou quando um atributo for adicionado / alterado ou removido, eu posso usar da seguinte maneira:

1
<div class="mydiv" data-js="div"></div>
1
2
3
4
5
6
7
8
9
10
11
var target = document.querySelector( '[data-js="div"]' );
var observer = new MutationObserver( handleMutationObserver );
var config = { childList: true, attributes: true };

function handleMutationObserver( mutations ) {
mutations.forEach(function(mutation) {
console.log( mutation.type );
});
}

observer.observe( target, config );

No meu HTML, adicionei uma div com um atributo data-js="div" e uma classe mydiv.

No JS, atribuímos esse nó para a variável target.
A variável observer será nosso observador. Então instanciamos o MutationObserver nessa varíável, passando como parâmetro a função de callback handleMutationObserver.

Depois criamos mais uma variável que receberá as configurações do que deve ser observado no elemento.
childList verifica mudanças nos filhos do nó correspondente. Se algum nó for adicionado ou removido do nó principal (target), será disparada a função de callback. Isso é válido também para nós de texto, não precisa ser somente tags.

attributes observa os atributos do elemento. Se algo mudar, o callback também é disparado.

Para ver todos os parâmetros que você pode utilizar, consulte esse link.

Faça o teste, jogando o código acima no seu console! :D
Só troque o target para um elemento válido :)

Se você não quiser mais observar um elemento, pode usar o método disconnect():

1
observer.disconnect();

Compatibilidade de browsers

Por ser algo relativamente novo, o MutationObserver só funciona em browsers modernos:

  • Chrome - 18+;
  • Firefox - 14+;
  • IE - 11+;
  • Opera - 15+;
  • Safari - 6+;

Polyfill

Para funcionar em todos os browsers, você pode usar um polyfill, feito com temporizadores. Não é o mais recomendado, mas funciona. O polyfill você encontra nesse link.

É possível fazer muita coisa com o MutationObserver. Recomendo que você dê uma lida na documentação completa dele aqui: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver

Até a próxima! :D

Sobre o #1postperday: https://blog.da2k.com.br/2014/12/31/um-post-por-dia/

Tem alguma sugestão para os próximos posts do #1postperday? Deixe ela aqui: https://github.com/fdaciuk/fdaciuk.github.io/issues/1