Mutation Observer API

Overview

The Mutation Observer API is used to monitor DOM changes. Any changes to the DOM, such as the increase or decrease of nodes, attribute changes, and text content changes, can be notified by this API.

Conceptually, it is very close to the event, which can be understood as a Mutation Observer event triggered when the DOM changes. However, it is essentially different from an event: the event is triggered synchronously, that is, the change of the DOM will trigger the corresponding event immediately; the Mutation Observer is triggered asynchronously, and the change of the DOM will not be triggered immediately, but wait until the current All DOM operations are finished before it is triggered.

This design is to cope with the frequent changes of the DOM. For example, if 1000 <p> elements are continuously inserted into the document, 1000 insertion events will be triggered continuously, and the callback function of each event will be executed. This is likely to cause the browser to freeze; while Mutation Observer is completely different , It will only be triggered after 1000 paragraphs have been inserted, and only once.

Mutation Observer has the following characteristics.

-It waits for all script tasks to be completed before running (ie asynchronous trigger mode). -It encapsulates the DOM change records into an array for processing, instead of processing DOM changes one by one. -It can observe all types of changes in the DOM, or specify to observe only a certain type of changes.

MutationObserver Constructor

When using it, first use the MutationObserver constructor to create a new observer instance, and specify the callback function of this instance at the same time.

var observer = new MutationObserver(callback);

The callback function in the above code will be called after each DOM change. The callback function accepts two parameters, the first is the change array, the second is the observer instance, the following is an example.

var observer = new MutationObserver(function (mutations, observer) {
  mutations.forEach(function (mutation) {
    console.log(mutation);
  });
});

MutationObserver instance method

observe()

The observe() method is used to start the monitoring, and it accepts two parameters.

-The first parameter: the DOM node to be observed -The second parameter: a configuration object that specifies the specific changes to be observed

var article = document.querySelector("article");

var options = {
  childList: true,
  attributes: true,
};

observer.observe(article, options);

In the above code, the observe() method accepts two parameters. The first is the DOM element to be observed is article, and the second is the type of change to be observed (sub-node changes and attribute changes).

The types of DOM changes that the observer can observe (ie the options object in the above code) are as follows.

-childList: Changes of child nodes (referring to new additions, deletions or changes). -attributes: Changes in attributes. -characterData: changes in node content or node text.

To observe which kind of change type, specify its value as true in the option object. It should be noted that at least one of these three observations must be specified at the same time. If none of them is specified, an error will be reported.

In addition to the change type, the options object can also set the following attributes:

-subtree: Boolean value, indicating whether to apply this observer to all descendants of this node. -attributeOldValue: Boolean value, indicating whether it is necessary to record the attribute value before the change when observing the change of attributes. -characterDataOldValue: Boolean value, indicating whether it is necessary to record the value before the change when observing the change of characterData. -attributeFilter: Array, representing specific attributes that need to be observed (such as ['class','src']).

// Start monitoring the changes of the document root node (ie <html> tag)
mutationObserver.observe(document.documentElement, {
  attributes: true,
  characterData: true,
  childList: true,
  subtree: true,
  attributeOldValue: true,
  characterDataOldValue: true,
});

Adding an observer to a node is just like using the addEventListener() method. Adding the same observer multiple times is invalid, and the callback function will still only be triggered once. If you specify a different options object, the one added later will prevail, similar to an override.

The following example is to observe the newly added child node.

var insertedNodes = [];
var observer = new MutationObserver(function (mutations) {
  mutations.forEach(function (mutation) {
    for (var i = 0; i < mutation.addedNodes.length; i++) {
      insertedNodes.push(mutation.addedNodes[i]);
    }
  });
  console.log(insertedNodes);
});
observer.observe(document, { childList: true, subtree: true });

disconnect(),takeRecords()

The disconnect() method is used to stop the observation. After calling this method, the DOM changes again and the observer will not be triggered.

observer.disconnect();

The takeRecords() method is used to clear change records, that is, no longer process unprocessed changes. This method returns an array of change records.

observer.takeRecords();

Below is an example.

// Save all changes that have not been processed by the observer
var changes = mutationObserver.takeRecords();

// stop watching
mutationObserver.disconnect();

MutationRecord Object

Every time the DOM changes, a change record (MutationRecord instance) is generated. This example contains all the information related to the change. What Mutation Observer deals with is an array of MutationRecord instances.

The MutationRecord object contains information about the DOM and has the following attributes:

-type: Observed change type (attributes, characterData or childList). -target: The DOM node that has changed. -addedNodes: Added DOM nodes. -removedNodes: Removed DOM nodes. -previousSibling: the previous sibling node, if there is no node, return null. -nextSibling: the next sibling node, if there is none, return null. -attributeName: the attribute that has changed. If the attributeFilter is set, only the pre-specified attributes are returned. -oldValue: The value before the change. This attribute is only valid for changes in attribute and characterData, if a change in childList occurs, null is returned.

Application example

Changes in child elements

The following example shows how to read the change record.

var callback = function (records) {
  records.map(function (record) {
    console.log("Mutation type: " + record.type);
    console.log("Mutation target: " + record.target);
  });
};

var mo = new MutationObserver(callback);

var option = {
  childList: true,
  subtree: true,
};

mo.observe(document.body, option);

The observer of the above code observes the changes of all the subordinate nodes of <body> (childList means observing child nodes, and subtree means observing descendant nodes). The callback function will display all the changed types and target nodes on the console.

Attribute changes

The following example shows how to track changes in attributes.

var callback = function (records) {
  records.map(function (record) {
    console.log("Previous attribute value: " + record.oldValue);
  });
};

var mo = new MutationObserver(callback);

var element = document.getElementById("#my_element");

var options = {
  attributes: true,
  attributeOldValue: true,
};

mo.observe(element, options);

The above code first sets the tracking attribute change ('attributes': true), and then sets the value before the record change. When the actual change occurs, the value before the change will be displayed on the console.

Replace the DOMContentLoaded event

When the webpage is loaded, the generation of DOM nodes will generate change records, so as long as you observe the changes of DOM, you can trigger related events at the first time, and there is no need to use the DOMContentLoaded event.

var observer = new MutationObserver(callback);
observer.observe(document.documentElement, {
  childList: true,
  subtree: true,
});

In the above code, the changes of the child nodes of document.documentElement (ie the <html>HTML node of the webpage) are monitored. The subtree attribute specifies that the monitoring also includes descendant nodes. Therefore, once any web page element is generated, it can be monitored immediately.

The following code uses the MutationObserver object to encapsulate a function that monitors DOM generation.

(function (win) {
  "use strict";

  var listeners = [];
  var doc = win.document;
  var MutationObserver = win.MutationObserver || win.WebKitMutationObserver;
  var observer;

  function ready(selector, fn) {
    // store selector and callback function
    listeners.push({
      selector: selector,
      fn: fn,
    });
    if (!observer) {
      // monitor document changes
      observer = new MutationObserver(check);
      observer.observe(doc.documentElement, {
        childList: true,
        subtree: true,
      });
    }
    // Check if the node is already in the DOM
    check();
  }

  function check() {
    // Check if it matches the stored node
    for (var i = 0; i < listeners.length; i++) {
      var listener = listeners[i];
      // Check if the specified node has a match
      var elements = doc.querySelectorAll(listener.selector);
      for (var j = 0; j < elements.length; j++) {
        var element = elements[j];
        // Make sure that the callback function will only be called once for the element
        if (!element.ready) {
          element.ready = true;
          // Call the callback function on this node
          listener.fn.call(element, element);
        }
      }
    }
  }

  // Expose ready
  win.ready = ready;
})(this);

// Instructions
ready(".foo", function (element) {
  // ...
});