// apis
MutationObserver, c'est quoi ? Le troisième observateur, et la file qui le rend utilisable
IntersectionObserver surveille la visibilité, ResizeObserver la taille, MutationObserver le DOM lui-même. Ce qu'il rapporte, pourquoi il a remplacé les Mutation Events, et la méthode qui révèle comment la livraison fonctionne réellement.
La plateforme compte trois observateurs, et ils se partagent le travail proprement. IntersectionObserver vous dit quand un élément entre dans la fenêtre d'affichage. ResizeObserver vous dit quand il change de taille. MutationObserver vous dit quand le DOM lui-même change.
MDN le formule en une ligne : l'interface « offre la possibilité de surveiller les changements apportés à l'arbre DOM ».
Ce qu'il peut surveiller
Trois types de changement, choisis par les options que vous passez :
childList— des nœuds ajoutés ou retirésattributes— un attribut modifié sur l'élément observécharacterData— le contenu textuel d'un nœud modifié
Plus subtree, qui étend n'importe lequel des précédents à tous les descendants plutôt qu'à la seule cible. Ce mot fait toute la différence entre surveiller un conteneur et surveiller tout ce qu'il contient.
const observer = new MutationObserver((records) => {
for (const record of records) {
if (record.type === 'childList') {
console.log(record.addedNodes.length, 'node(s) added');
}
}
});
observer.observe(targetNode, {
attributes: true,
childList: true,
subtree: true,
}); Pourquoi il a remplacé les Mutation Events
MDN est explicite sur la filiation : MutationObserver « est conçu comme un remplacement de l'ancienne fonctionnalité Mutation Events, qui faisait partie de la spécification DOM3 Events ».
Les Mutation Events se déclenchaient de façon synchrone, un événement par changement, au milieu de ce qui modifiait le DOM. Un script insérant cent nœuds produisait cent interruptions, et un gestionnaire qui touchait au DOM pouvait déclencher d'autres événements alors qu'il traitait encore le premier. Ils ont été retirés pour de bonnes raisons.
La file d'attente est la conception
La partie intéressante n'est pas le callback mais ce qui l'alimente, et une méthode le trahit. takeRecords() « retire toutes les notifications en attente de la file de notifications du MutationObserver et les renvoie dans un nouveau tableau d'objets MutationRecord ».
Une file de notifications en attente signifie que la livraison n'est pas immédiate. Les changements s'accumulent, et votre callback reçoit un tableau d'enregistrements plutôt qu'un appel par changement. C'est précisément ce qui rend l'API utilisable là où les Mutation Events ne l'étaient pas : cent insertions deviennent un callback avec cent enregistrements, et votre code ne peut pas interrompre l'opération qui les a causés.
Cela explique aussi quand takeRecords() gagne sa place. Avant d'appeler disconnect(), tout ce qui est déjà en file mais pas encore livré serait perdu : vider la file d'abord permet de le traiter.
Deux choses qui piègent
Vos propres écritures sont observées aussi. Si le callback modifie le DOM à l'intérieur du sous-arbre observé, ces modifications entrent en file et vous rappellent. Le remède habituel est un drapeau de garde, ou un resserrement des options pour que vos propres écritures tombent hors de ce que vous surveillez.
Les enregistrements d'attribut ne portent pas la nouvelle valeur par défaut. Vous obtenez le nom de l'attribut, et attributeOldValue: true dans les options si vous voulez la précédente. La valeur actuelle, vous la lisez vous-même sur l'élément.
Quand y recourir
MutationObserver est le bon outil quand le DOM est modifié par quelque chose que vous ne contrôlez pas : un widget tiers, un bloc injecté par un CMS, un éditeur. C'est le mauvais outil pour l'état de votre propre application, où vous savez déjà quand les choses changent et où les observer revient à vous poser lentement une question que vous pouvez trancher directement.
Et disconnect() compte plus qu'il n'en a l'air : MDN le décrit comme arrêtant les notifications « jusqu'à ce qu'observe() soit appelé de nouveau, et seulement dans ce cas ». Un observateur posé sur un sous-arbre et qui survit au composant qui le surveillait est une fuite que personne ne remarque avant une heure de page ouverte.
Un bloc-notes en attente d'être lu. takeRecords() fait exactement cela : il vide la file d'attente de l'observateur et vous remet ce qui s'y est accumulé depuis la dernière livraison.