MutationObserver API
debt(d7/e3/b5/t7)
Closest to 'only careful code review or runtime testing' (d7). MutationObserver misuse (infinite loops, missing disconnect) won't be caught by automated tools—detection_hints.automated is false, and linters don't detect observer lifecycle bugs. Issues only surface through careful testing or production observation.
Closest to 'simple parameterised fix' (e3). The quick_fix explicitly states: 'Replace polling with MutationObserver for DOM change detection. Disconnect when no longer needed.' Fixing common mistakes (adding disconnect, preventing DOM-modifying callbacks) requires simple refactoring within one component, not cross-cutting changes.
Closest to 'persistent productivity tax' (b5). MutationObserver affects reactive DOM patterns across multiple features (progressive enhancement, third-party script integration per why_it_matters). Once chosen, subsequent developers must understand observer lifecycle, avoid infinite loops, and manage cleanup in every instance. The tax is system-wide but not architectural.
Closest to 'serious trap' (t7). The misconception field directly states the canonical trap: 'MutationObserver fires synchronously for each change — it batches mutations and fires the callback asynchronously after the current script completes.' This contradicts the intuitive, imperative mental model many developers bring from other event systems, making async batching behaviour a serious gotcha that requires learning.
TL;DR
Explanation
MutationObserver fires when the observed DOM subtree changes. Config options: childList (child add/remove), attributes (attribute changes), subtree (observe all descendants), characterData (text changes), attributeFilter (specific attributes). Each callback receives MutationRecord[] with type, target, addedNodes, removedNodes, attributeName, oldValue. Use cases: third-party content monitoring, auto-initializing widgets on dynamic HTML, form auto-save on change. Performance: batches mutations and calls callback asynchronously. Alternative to legacy DOMSubtreeModified event.
Common Misconception
Why It Matters
Common Mistakes
- Not disconnecting on cleanup — observes forever.
- Infinite loops: modifying the DOM inside the callback triggers the observer again.
- Observing document.body with subtree:true — very expensive on large pages.
Code Examples
// Polling instead of observing:
setInterval(() => {
if (document.querySelector('.new-content')) {
initWidget();
}
}, 100);
const observer = new MutationObserver(mutations => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.classList?.contains('widget')) initWidget(node);
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
// Cleanup: observer.disconnect();