# Mutation observer `MutationObserver` is a built-in object that observes a DOM element and fires a callback in case of changes. We'll first see syntax, and then explore a real-world use case. ## Syntax `MutationObserver` is easy to use. First, we create an observer with a callback-function: ```js let observer = new MutationObserver(callback); ``` And then attach it to a DOM node: ```js observer.observe(node, config); ``` `config` is an object with boolean options "what kind of changes to react on": - `childList` -- changes in the direct children of `node`, - `subtree` -- in all descendants of `node`, - `attributes` -- attributes of `node`, - `attributeOldValue` -- record the old value of attribute (infers `attributes`), - `characterData` -- whether to observe `node.data` (text content), - `characterDataOldValue` -- record the old value of `node.data` (infers `characterData`), - `attributeFilter` -- an array of attribute names, to observe only selected ones. Then after any changes, the `callback` is executed, with a list of [MutationRecord](https://dom.spec.whatwg.org/#mutationrecord) objects as the first argument, and the observer itself as the second argument. [MutationRecord](https://dom.spec.whatwg.org/#mutationrecord) objects have properties: - `type` -- mutation type, one of - `"attributes"`: attribute modified - `"characterData"`: data modified, used for text nodes, - `"childList"`: child elements added/removed, - `target` -- where the change occurred: an element for "attributes", or text node for "characterData", or an element for a "childList" mutation, - `addedNodes/removedNodes` -- nodes that were added/removed, - `previousSibling/nextSibling` -- the previous and next sibling to added/removed nodes, - `attributeName/attributeNamespace` -- the name/namespace (for XML) of the changed attribute, - `oldValue` -- the previous value, only for attribute or text changes. For example, here's a `
` with `contentEditable` attribute. That attribute allows us to focus on it and edit. ```html run
Click and edit, please
``` If we change the text inside `me`, we'll get a single mutation: ```js mutationRecords = [{ type: "characterData", oldValue: "me", target: , // other properties empty }]; ``` If we select and remove the `me` altogether, we'll get multiple mutations: ```js mutationRecords = [{ type: "childList", target: , removedNodes: [], nextSibling: , previousSibling: // other properties empty }, { type: "characterData" target: // ...details depend on how the browser handles the change // it may coalesce two adjacent text nodes "Edit " and ", please" into one node // or it can just delete the extra space after "Edit". // may be one mutation or a few }]; ``` ## Observer use case When `MutationObserver` is needed? Is there a scenario when such thing can be useful? We can track something like `contentEditable` and implement "undo/redo" functionality (record mutations and rollback/redo them on demand). There are also cases when `MutationObserver` is good from architectural standpoint. Let's say we're making a website about programming. Naturally, articles and other materials may contain source code snippets. An HTML markup of a code snippet looks like this: ```html ...

  // here's the code
  let hello = "world";
... ``` Also we'll use a JavaScript highlighting library on our site, e.g. [Prism.js](https://prismjs.com/). A call to `Prism.highlightElem(pre)` examines the contents of such `pre` elements and adds into them special tags and styles for colored syntax highlighting, similar to what you see in examples here, at this page. When to run that method? We can do it on `DOMContentLoaded` event, or at the bottom of the page. At that moment we have DOM ready, can search for elements `pre[class*="language"]` and call `Prism.highlightElem` on them: ```js // highlight all code snippets on the page document.querySelectorAll('pre[class*="language"]').forEach(Prism.highlightElem); ``` Now the `
` snippet looks like this (without line numbers by default):

```js
// here's the code
let hello = "world";
```

Everything's simple so far, right? There are `
` code snippets in HTML, we highlight them.

Now let's go on. Let's say we're going to dynamically fetch materials from a server. We'll study methods for that [later in the tutorial](info:fetch-basics). For now it only matters that we fetch an HTML article from a webserver and display it on demand:

```js
let article = /* fetch new content from server */
articleElem.innerHTML = article;
```

The new `article` HTML may contain code snippets. We need to call `Prism.highlightElem` on them, otherwise they won't get highlighted.

**Where and when to call `Prism.highlightElem` for a dynamically loaded article?**

We could append that call to the code that loads an article, like this:

```js
let article = /* fetch new content from server */
articleElem.innerHTML = article;

*!*
let snippets = articleElem.querySelectorAll('pre[class*="language-"]');
snippets.forEach(Prism.highlightElem);
*/!*
```

...But imagine, we have many places in the code where we load contents: articles, quizzes, forum posts. Do we need to put the highlighting call everywhere? That's not very convenient, and also easy to forget.

And what if the content is loaded by a third-party module? E.g. we have a forum written by someone else, that loads contents dynamically, and we'd like to add syntax highlighting to it. No one likes to patch third-party scripts.

Luckily, there's another option.

We can use `MutationObserver` to automatically detect code snippets inserted in the page and highlight them.

So we'll handle the highlighting functionality in one place, relieving us from the need to integrate it.

## Dynamic highlight demo

Here's the working example.

If you run this code, it starts observing the element below and highlighting any code snippets that appear there:

```js run
let observer = new MutationObserver(mutations => {

  for(let mutation of mutations) {
    // examine new nodes

    for(let node of mutation.addedNodes) {
      // we track only elements, skip other nodes (e.g. text nodes)
      if (!(node instanceof HTMLElement)) continue;

      // check the inserted element for being a code snippet
      if (node.matches('pre[class*="language-"]')) {
        Prism.highlightElement(node);
      }

      // maybe there's a code snippet somewhere in its subtree?
      for(let elem of node.querySelectorAll('pre[class*="language-"]')) {
        Prism.highlightElement(elem);
      }
    }
  }

});

let demoElem = document.getElementById('highlight-demo');

observer.observe(demoElem, {childList: true, subtree: true});
```

Demo element with id="highlight-demo", obverved by the example above.

The code below populates `innerHTML`. Please run the code above first, it will watch and highlight the new content: ```js run let demoElem = document.getElementById('highlight-demo'); // dynamically insert content with code snippets demoElem.innerHTML = `A code snippet is below:
 let hello = "world!"; 
Another one:
.class { margin: 5px; } 
`; ``` Now we have `MutationObserver` that can track all highlighting in observed elements or the whole `document`. We can add/remove code snippets in HTML without thinking about it. ## Additional methods There's a method to stop observing the node: - `observer.disconnect()` -- stops the observation. Additionally: - `mutationRecords = observer.takeRecords()` -- gets a list of unprocessed mutation records, those that happened, but the callback did not handle them. ```js // we're going to disconnect the observer // it might have not yet handled some mutations let mutationRecords = observer.takeRecords(); // process mutationRecords // now all handled, disconnect observer.disconnect(); ``` ## Garbage collection Observers use weak references to nodes internally. That is: if a node is removed from DOM, and becomes unreachable, then it becomes garbage collected, an observer doesn't prevent that. ## Summary `MutationObserver` can react on changes in DOM: attributes, added/removed elements, text content. We can use it to track changes introduced by other parts of our own or 3rd-party code. For example, to post-process dynamically inserted content, as demo `innerHTML`, like highlighting in the example above.