As a front-end developer, do you know about MutationObserver?

Table of contents

foreword

Evolution

basic concept

MutationObserver

observe(target, options)

attributes: Whether to monitor the label attribute changes

childList: Whether to monitor child node changes

characterData: Whether to monitor the change of the content of the text node

attributeOldValue: Whether to record the value before the attribute change

characterDataOldValue: Whether to record the value before the content of the text node changes

subtree: Whether to monitor the changes of descendant nodes

attributeFilter: filter attribute name

disconnect()

takeRecords()

MutationRecord[]

Properties of MutationRecord

Application scenarios of MutationObserver

Detect and respond to DOM changes

Dynamic Style Changes

Communication between tags

shortcoming

The first is performance loss

Second is operational conflict

Finally, it is impossible to monitor changes in IFrame

Summarize


foreword

MutationObserver may not be commonly used in development, but it can indeed solve some problems in special cases. It is somewhat similar to addEventListener, when the user triggers certain event operations, the corresponding callback will be called

A few days ago, I used MutationObserver in the requirement iteration. Since the pop-up window in the early version of Antd did not respond, and many pop-up windows in the code were not re-encapsulated, it was impossible to know when the pop-up window appeared and disappeared. So I used the front-end Hack to take advantage of the way to monitor element changes to solve such problems. Here is a knowledge sharing

So what exactly is a MutationObserver? how to use? What role does it play in development? What are the hidden dangers of using this API? please watch the following part

Evolution

Before Mutation was standardized, the unofficial way for developers to monitor DOM changes was to use the timer (polling) mechanism to create macro tasks through setTimeout or setInterval to observe changes in nodes;

In addition, some scenarios can also monitor operations and changes through the event delegation mechanism addEventListener

Later, the emergence of MutationEvent enhanced the scalability and limitations of DOM monitoring, and standardized Mutation. However, MutationEvent adopts a synchronous method and triggers callbacks in real time, that is, each change will trigger the monitoring callback function, which is very lossy in performance.

So there is the current MutationObserver . MutationObserver, like Promise, belongs to the microtask queue. It uses an asynchronous monitoring method. All operations will be placed in the callback. When there is an operation, the monitoring callback will be triggered when the next microtask is executed; or it can be understood as: when a node performs multiple operations at the same time, its changes will be recorded in an asynchronous queue and finally displayed at one time. This will not affect page loading, but also ensures the monitoring of DOM changes

basic concept

MutationObserver is a JS API that can be used to observe changes in the DOM tree in a document and execute specific callback functions when these changes occur.

Introduce the basic usage. The MutationObserver class receives a callback function, which is triggered when the label changes. The parameter mutationsList is an array of MutationRecord objects (more on this later), and the parameter observer is the instance object of the current MutationObserver; there is a function observe in the observer instance, and two parameters are passed in. The first is the label to be monitored, and the second is the configuration item that mainly declares which attributes to monitor, such as childList, attributes, etc.

const elem = document.querySelector("#elem");
// 创建观察者实例
const observer = new MutationObserver((mutationsList, observer) => {
    // 监听回调
    console.log(mutationsList, observer);
});
observer.observe(elem, {
    //至少要传一个配置
    attributes: true,
});
// 元素发生改变
elem.hidden = true;

MutationObserver

Instances of the MutationObserver class have the following functions

observe(target, options)

Observe the specified target element. The second parameter passes in a configuration object to specify the type of event to listen to and other options

The configuration can pass in the following options:

attributes: Whether to monitor the label attribute changes

When introducing the basic usage, we exemplified the attributes configuration. When the hidden attribute changes, the monitoring callback will be triggered

childList: Whether to monitor child node changes

Following the sample code above, we change the configuration of observe to childList: true, so that we can monitor the changes of child nodes

  <body>
    <div id="elem"></div>
    <div id="son"></div>
    <script type="text/javascript">
      const elem = document.querySelector("#elem");
      const son = document.querySelector("#son");
      const observer = new MutationObserver((mutationsList, observer) => {
        console.log(mutationsList, observer);
      });
      observer.observe(elem, {
        childList: true,
      });
      elem.textContent = "小黑";
      elem.appendChild(son);
      elem.removeChild(son);
    </script>
  </body>

characterData: Whether to monitor the change of the content of the text node

It is worth noting that the text node is a child node of the label, so first we need to monitor the child nodes of the label to change, such as

const elem = document.querySelector("#elem");
const textElem = elem.firstChild; // 这里获取标签的文本节点
const observer = new MutationObserver((mutationsList, observer) => {
  console.log(mutationsList, observer);
});
observer.observe(textElem, {
  characterData: true,
});
textElem.textContent = "小黑";

attributeOldValue: Whether to record the value before the attribute change

attributeOldValue must be used in conjunction with attribute, we first monitor the attribute change of the label.

  <body>
    <div id="elem" name="阿黄"></div>
    <script type="text/javascript">
      const elem = document.querySelector("#elem");
      const observer = new MutationObserver((mutationsList, observer) => {
        console.log(mutationsList, observer);
      });
      observer.observe(elem, {
        attributes: true,
      });
      elem.setAttribute("name", "小黑");
    </script>
  </body>

When we listen to the attributes property, we will find that oldValue is null

 

if we add

observer.observe(elem, {
  attributes: true,
  attributeOldValue: true,
});

will store the original attribute value 

characterDataOldValue: Whether to record the value before the content of the text node changes

Similar to attributeOldValue, characterDataOldValue is used to record and store the original text value. We change the text to black, and you can see that the value of oldValue in the callback is the previous A Huang

const elem = document.querySelector("#elem");
const textElem = elem.firstChild;
const observer = new MutationObserver((mutationsList, observer) => {
    console.log(mutationsList, observer);
});
observer.observe(textElem, {
    characterData: true,
    characterDataOldValue: true,
});
textElem.textContent = "小黑";

subtree: Whether to monitor the changes of descendant nodes

Let's still take the above code as an example. If there are two divs nested and you want to monitor the bottom div change, you can add the attribute subtree and the attribute to be monitored, such as monitoring the attribute changes of all descendant nodes.

  <body>
    <div id="elem">
      <div>
        <div></div>
      </div>
    </div>
    <script type="text/javascript">
      const elem = document.querySelector("#elem");
      const child = elem.firstElementChild.firstElementChild;
      const observer = new MutationObserver((mutationsList, observer) => {
        console.log(mutationsList, observer);
      });
      observer.observe(elem, {
        subtree: true,
        attributes: true,
      });
      child.setAttribute("name", "阿黄");
    </script>
  </body>

attributeFilter: filter attribute name

While configuring attributes to monitor attribute changes, you can use the attributeFilter configuration item to filter attribute names. AttributeFilter filters by passing in a string array [ "class","name" ]. For example, I only want to monitor changes in class names

  <body>
    <div id="elem"></div>
    <script type="text/javascript">
      const elem = document.querySelector("#elem");
      const observer = new MutationObserver((mutationsList, observer) => {
        console.log(mutationsList, observer);
      });
      observer.observe(elem, {
        attributes: true,
        attributeFilter: ["class"],
      });
      elem.setAttribute("name", "阿黄");
      elem.setAttribute("class", "elem");
      elem.hidden = true;
    </script>
  </body>

At this time, only the callback after the class is modified will be displayed

disconnect()

When we need to cancel the monitoring of label changes, we can use the disconnect() function of the instantiated object MutationObserver to interrupt. Since the Dom tree changes are asynchronous, we use a delay to trigger the cancellation of monitoring.

const elem = document.querySelector("#elem");
const observer = new MutationObserver((mutationsList, observer) => {
    console.log(mutationsList, observer);
});
observer.observe(elem, {
    attributes: true,
});
elem.setAttribute("name", "阿黄");
setTimeout(() => {
    observer.disconnect();
    elem.hidden = true;
});

takeRecords()

The first parameter in the callback function is the mutationsList array. At this time, if we want to clear this array, we can use the takeRecords function to achieve the reset effect.

const elem = document.querySelector("#elem");
const observer = new MutationObserver((mutationsList, observer) => {
    console.log(mutationsList, observer);
});
observer.observe(elem, {
    attributes: true,
});
elem.setAttribute("name", "阿黄");
elem.hidden = true;
observer.takeRecords();
elem.setAttribute("name", "小黑");

After the above code is run, only the operation whose name is set to black will be printed

MutationRecord[]

When the MutationObserver class is instantiated, an observer callback function is passed in. Its first parameter is a MutationRecord array, which receives the changed element information

Properties of MutationRecord

target: the changed node

type: the type of change

  • attributes: attributes are added, modified or deleted
  • characterData: the text of the label changes
  • childList: child nodes are added, modified order or deleted

nextSibling: a sibling node after the child node of the parent node (insertBefore, removeChild)

previousSibling: the previous sibling node of the child node of the parent node (appendChild)

attributeName: When type is attributes, it indicates the changed attribute name (setAttribute)

attributeNamespace: When type is attributes, it indicates the changed attribute namespace name (setAttributeNS)

addedNodes: added nodes

removedNodes: removed nodes

oldValue: the old value recorded when attributeOldValue or characterDataOldValue is true

The following piece of code covers almost all the above attributes, you can refer to it

  <body>
    <div id="elem" name="阿黄">elem</div>
    <div id="son">son</div>
    <div id="prev">prev</div>
    <div id="next">next</div>
    <script type="text/javascript">
      const elem = document.querySelector("#elem");
      const son = document.querySelector("#son");
      const prev = document.querySelector("#prev");
      const next = document.querySelector("#next");
      const elemText = elem.firstChild;
      const observer = new MutationObserver((mutationsList, observer) => {
        console.log(mutationsList);
      });
      observer.observe(elem, {
        attributes: true,
        attributeOldValue: true,
        characterData: true,
        characterDataOldValue: true,
        subtree: true,
        childList: true,
      });
      // type: "characterData", oldValue: "elem"
      elemText.textContent = "阿黄";
      // oldValue: "阿黄", type: "attributes", attributeName :  "name"
      elem.setAttribute("name", "小黑");
      // attributeName: "name", attributeNamespace: "ns", type: "attributes"
      elem.setAttributeNS("ns", "NS:name", "阿黄");
      // type: "childList", removedNodes: NodeList[text], addedNodes: NodeList[text]
      elem.textContent = "小黑";
      // addedNodes:NodeList[div#prev], type: "childList"
      elem.appendChild(prev);
      // addedNodes:NodeList[div#next], type: "childList", previousSibling: div#prev
      elem.appendChild(next);
      // addedNodes: NodeList[div#son], type: "childList", previousSibling: div#prev, nextSibling: div#next
      elem.insertBefore(son, next);
      // removedNodes: NodeList[div#son], type: "childList", previousSibling: div#prev, nextSibling: div#next
      elem.removeChild(son);
    </script>
  </body>

Application scenarios of MutationObserver

Here are some common scenarios

Detect and respond to DOM changes

For example, use MutationObserver to implement lazy loading of images, monitor the visibilitychange event of the img tag, and respond; or load images when the offset top of the element is in the window

Dynamic Style Changes

Monitor style or class changes to respond, such as my previous application: monitor antd's modal window changes, and make follow-up operations

Communication between tags

Send and receive messages by listening to changes in the data-key attribute

shortcoming

MutationObserver is easy to use, but its shortcomings are also obvious

The first is performance loss

Although many optimizations have been made on the basis of MutationEvent, the operation of monitoring the body still has a great impact on performance, and all user operations may cause the function to be called back frequently.

The solution is to monitor a small range of nodes as much as possible, or limit the type of monitoring

Second is operational conflict

Due to the non-uniqueness of the callback function, if there is a dependency between two observers listening to the changed operation, it may cause errors or conflicts

The solution can be to use the lock mechanism, when the two conditions are met to enter the function or thread

Finally, it is impossible to monitor changes in IFrame

The MutationObserver operation is monitored based on the current DOM, so it cannot cross threads and windows

You can use postmessage for communication operations, you can refer to a previous article about window and thread communication

Summarize

This article introduces the basic concept and use of the MutationObserver class. The way to monitor the DOM is from the earliest timer and event delegation to MutationEvent and finally to the MutationObserver introduced in this article; it uses an asynchronous non-real-time monitoring method, and the monitoring callback returns a MutationRecord list to record the operation changes of the Dom; in addition, we can monitor a node through the observe of the instance. The types of monitoring mainly include attributes (attributes), childList (child node changes), characterData ( Text node changes), other configuration items include attributeOldValue (record attribute old value), characterDataOldValue (record text old value), subtree (monitor descendant nodes), attributeFilter (attribute name filtering); finally introduces the application scenarios and disadvantages of MutationObserver. The application scenario is mainly to monitor DOM changes and take corresponding operations. The disadvantages are mainly: performance loss, operation conflicts, and thread restrictions;

The above is the entire content of the article, I hope it will be helpful to you, if you think the article is good, please support the author three times, thank you very much!

Guess you like

Origin blog.csdn.net/time_____/article/details/130508329
Recommended