In the last section, we talked about whether DOM operations are “synchronous” or “asynchronous”, and how to tell exactly when DOM renders are successful.

What is a MutationObserver

Interfaces provide the ability to monitor changes made to the DOM tree. It is designed as a replacement for the older MutationEvents functionality, which is part of the DOM3 Events specification.

Source: the MDN

Simply listen for changes in the DOM tree.

So, what are the MutationEvents being replaced?

Second, the MutationEvents

  1. Let’s be clear:MutationEventsIt’s also written in MDN, byDOM EventAcknowledging flaws in the API,To oppose the use of.
  2. At the heart of the defect are two things: cross-browser support and performance issues
  3. MutationEventsThe principle of:Listen to the DOM through binding eventsAt first glance, it seems normal. Let’s list the related monitoring events:
DOMAttributeNameChanged
DOMCharacterDataModified
DOMElementNameChanged
DOMNodeInserted
DOMNodeInsertedIntoDocument
DOMNodeRemoved
DOMNodeRemovedFromDocument
DOMSubtreeModified
Copy the code
  1. Don’t remember, with so many events, it will take forever for browsers with different kernel versions to be compatible.
  2. Specific performance issues (highlighted) :
    • (1) Many events, visible, monitoring a number of binding.
    • (2) As long as it is a binding event, it can not be separated from bubbling or capturing, and the significance of listening may be the response of a large number of frequent changes. Besides, what if there are multiple taps? What if you have multiple nested layers listening on each layer? What if father and son, brother and ancestor all barrel? (Write out to maintain a try?)
    • (3) The immediate execution of the binding event may interrupt the remaining changes to the DOM, that is, the event is triggered before it is finished.

Original author makes fun of email

  1. In addition, these questions actually useObserver modelI can do it pretty well. So, lookMutationObserverI’ll get the name.

The observer model is described in one sentence

This article mainly introduces MutationObserver, as a design principle of MutationObserver

If A wants to read news, A will ‘pay (subscribe)’ at B’s first, and then B will send newspapers to A when there is news, and A will choose what news he wants to read

  • A – Subscriber – YesMutationObserverGet the return value (get the newspaper) – there can be an infinite number of A’s to look at B
  • B – observed –DOM– Send new content when changing (send new newspaper) – B decide A

Of course, more detailed observer models will be available in the near future.

4. Improvement of MutationObserver

In view of the above defects of MutationEvents, here are the advantages of MutationObserver.

  1. Browser compatibility issues:
const mutationObserver = new MutationObserver(callback);
Copy the code

MutationObserver is a constructor that is not compatible with Internet Explorer 11 or later. It is worth noting that mobile compatibility is better.

  1. Event multiple issues:
const mutationObserver = new MutationObserver((mutations, observer) => { console.log(observer); Log (mutations); // change the array treatment. forEach(function(mutation) {
	    console.log(mutation);
	});
});
Copy the code

Callback, as a listening event, returns mutations and observer, two fixed parameters. Mutations – array observer – observer instance

So what’s the function that’s going to be executed? Look down

  1. Immediate trigger/multiple trigger problem:
function editContent() {
    const content = document.getElementById('content'); console.log(1); // -------------------------- observer(); / / subscribe / / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- content. style. Background ='lightblue';
    content.style.background = 'red';
    console.log(2);
    content.innerHTML = 4433;
    console.log(3);
    const newNode = document.createElement('p');
    newNode.innerHTML = 888888;
    content.appendChild(newNode);
    console.log(4);
}
Copy the code

Execution Result:

// 1
// 2
// 3
// 4
// MutationObserver {}
// (4)[MutationRecord, MutationRecord, MutationRecord, MutationRecord]
Copy the code
  • Execute asynchronously, insert microtask queue, execute after script is executed. (Posts where microtasks are no longer clear)
  • mutationsParameter will listenDOMClick on all change recordsExecution orderEncapsulate into aAn array ofTo return.
  • You can listen to the target through configuration itemsDOMUnder theChild elementsChange record of

Basic use of MutationObserver

You have seen above how to create an instance object through the MutationObserver constructor. The next step is to bind the observed and what changes need to be observed.

1. MutationObserver.observe(dom, options)

Start listening and receive two parameters.

  • First parameter: observedDOMnode
  • Second parameter: Configure the options that need to be observed (rememberMutationEventsA lot of events, this is done through configuration items.
mutationObserver.observe(content, {
    attributes: true, // Boolean - Observing target attributes changing characterData:true, // Boolean - Observe the change of target data (data/value before change) childList:true, // Boolean - Observe changes to target child nodes, such as adding or deleting target child nodes, not including changes to child nodes and child node descendants subtree:true, // Boolean - The target and its descendants will observe attributeOldValue:true, // Boolean - indicates the characterDataOldValue of the target attribute before the change is recorded:true, // Boolean - Setting characterDataOldValue can be omitted from characterData setting // attributeFilter: [characterData: ['src'.'class'] // array-view specified properties});Copy the code

Note:

  1. attributeFilter/attributeOldValuePriority overattributes
  2. characterDataOldValuePriority overcharacterData
  3. attributes/characterData/childList(or higher specific items) at least one item istrue;
  4. The specific item exists, and the corresponding option canignoreOr must betrue

The attached:Development API

2. MutationObserver.disconnect()

Stop looking. Note: After listening, remember to unsubscribe as much as possible

3. MutationObserver.takeRecords()

Clear change records. That is, unprocessed changes are no longer processed. This method returns an array of change records, and notice that it takes effect immediately.

Attached: takeRecords change record field contentMutationRecordobject

/*
MutationRecord = {
  type: Returns if the property changes"attributes"If it is a CharacterData node (Text node, Comment node) change, return"characterData", node tree changes return"childList"AddedNodes: returns the list of addedNodes removedNodes: returns the list of deleted nodes previousSibling: returns the last sibling of the node added or removed respectively, otherwise returns null nextSibling: Returns the next sibling of the node added or removed, otherwise null attributeName: returns the local name of the changed attribute, otherwise Null attributeNamespace: returns the namespace of the changed attribute, otherwise null oldValue: The return value depends ontype. for"attributes", which changes the value of the previous property. for"characterData", which changes the data of the previous node. for"childList"It is null} */Copy the code

6. Advanced application of MutationObserver

  • Listen for JS script creationDOM rendering complete
  • Monitor image/rich text editor/node content changes and processing
  • aboutvueforMutationObserverThe application of
1. Listen to the COMPLETION of DOM rendering created by the JS script

As mentioned earlier, something similar to “asynchronous” happens when a script blocks DOM rendering, affecting subsequent DOM operations. Although this can be done by triggering backflow, it is not a very good choice in complex business scenarios/excessive data scenarios. Since the MutationObserver can listen for changes in the DOM tree’s subnodes, you can use this to listen for changes in the DOM tree of the Document or parent node.

Small chestnuts:

// html
<div id="content">66666</div>

// js
let time = 4;
let arr = new Array(time);
let content = document.getElementById('content');
letmutationObserver = new MutationObserver(obsCallback); // create instance obs(); // bind the observer to obstruct(); // Execute block // complete creationfunctionObsCallback (mutations, observer) {console.log(' create complete! `); console.log(observer); Log (mutations); // change array}function obs() {
    mutationObserver.observe(content, {
	    childList: true,
	    subtree: true}); }function obstruct() {
  for (let i = 0; i < arr.length; i++) {
		arr[i] = `<div>${i}</div>`;
	}

	arr.map((item, idx) => {
		for(let i = 0; i < 3000; i++) console.log(1)
		content.innerHTML += item;
	});
}

Copy the code
2. Monitor picture/rich text editor/node content changes and processing

A previous article talked about the contenteditable property, which makes the DOM editable, rich text editor, and so on. For such applications, operations such as filtering keywords or content, blocking edits (content restoration), and unremovable image watermarks can be easily implemented (Attachment 1).

The humble chestnut that prevents editing:

// html
<div id="content">66666</div>

// js
functionobsCallback(mutations, observer) { console.log(observer); Log (mutations); Mutations. ForEach (mutation => {if (mutation.target.contentEditable === 'true') {
            mutation.target.setAttribute('contenteditable'.'false'); }})}function obs() {
    mutationObserver.observe(content, {
        // attributes: true,
        attributeFilter: ['contenteditable']
        // characterData: true,
        // childList: true,
        // subtree: true}); }Copy the code

The attached:The watermark can not be deleted

About 3.vueforMutationObserverThe application of
  • Before Vue2.0, the application of VUE framework to MutationObserver was nextTick. The principle is to use the MutationObserver asynchronous callback functions to queue up microtasks. To do this, create a new node and observe it, updating its contents at will.

  • What? Why is a MessageChannel used before 2.0? What is a MessageChannel? That’s the next topic.

  • Why MutationObserver is used, or how it differs from Promise and setTimeout. Vue priorities are Promise, MutationObserver, and setTimeout. When a Promise is incompatible, select MutationObserver, which is basically the same in terms of functionality and performance, with the exception of creating a new node and moving it around. SetTimeout is finally used as an alternative for compatibility, for the following reasons.

  • Reason: MutationObserver and Promise belong to microtask, setTimeout belongs to macro task. In the browser execution mechanism, the macro task is re-rendered every time it finishes executing, while the microtask is executed in the current macro task to get the latest update as soon as possible, and the corresponding DOM operation (recall from the previous article) is completed when the macro task finishes. However, with the setTimeout macro task, updates wait for all previous macro tasks in the queue to complete, and if there is a DOM operation in the update, the browser will render it twice.

  • Deprecated reason: a compatibility BUG. In the case of iOS UIWebView, the page stops running for a period of time. Currently, the native MutationObserver does not have a good solution, and it would be unnecessary to separate iOS 10 Safari from other runtime environments. (For a better compatible) original reply


Reference:

  1. Developer.mozilla.org/en-US/docs/…
  2. Segmentfault.com/a/119000001…
  3. Javascript.ruanyifeng.com/dom/mutatio…
  4. Docs.microsoft.com/en-us/previ…