JavaScript advanced programming - Chapter 14: DOM

Chapter 14: DOM

Document Object Model is the programming interface of HTML and XML documents; That is, developers can add, delete and modify various parts of the page through JavaScript programming;

14.1 node level

Any HTML\XML document can be represented as a hierarchy of nodes.

<html>
    <head>
        <title>Sample Page</title>
    </head>
    <body>
        <p>Hello World!</p>
    </body>
</html>

In HTML, each document can only have one document element, and it is the outermost element, which has and has only one direct child element < HTML >

14.1.1 Node type

DOM Level 1 describes the interface of Node, which must be implemented by all DOM Node types;

The Node interface is implemented as a Node type in JavaScript.

In JavaScript, all Node types inherit the Node type. There are 12 types and are represented by 12 constants

(numbers can also be used instead):

  • Node.ELEMENT_NODE(1)

  • Node.ATTRIBUTE_NODE(2)

  • Node.TEXT_NODE(3)

  • Node.CDATA_SECTION_NODE(4)

  • Node.ENTITY_REFERENCE_NODE(5)

  • Node.ENTITY_NODE(6)

  • Node.PROCESSING_INSTRUCTION_NODE(7)

  • Node.COMMENT_NODE(8)

  • Node.DOCUMENT_NODE(9)

  • Node.DOCUMENT_TYPE_NODE(10)

  • Node.DOCUMENT_FRAGMENT_NODE(11)

  • Node.NOTATION_NODE(12)

Node information

  • nodeName: stores the name of the node, such as div and img
  • nodeValue: stores the value of the node. If it is a label, the value is null
  • nodeType: stores node types, corresponding to the above 12 types

Node relationship

Each node in the document has a relationship, which is equivalent to a genealogy. There are two differences: parent element and child element;

  1. childNodes: each node has this attribute, which contains an instance of NodeList. It is an array like object that orderly stores all the direct child nodes of the node. The NodeList instance is a real-time active object, which means that the DOM tree and NodeList will change, and vice versa;
    • Because it is an array like object, you can access its elements through [];
    • You can also obtain the number of child elements through the length attribute.
  2. parentNode: points to the parent node
  3. Previous sibling: points to the previous sibling node, childNodes [0] previousSibling is null
  4. nextSibling: points to the next sibling node, childNodes [childNodes. Length-1] nextSibling is null
  5. firstChild/lastChild: points to the first child node and the last child node
  6. hasChildNodes(): returns true, indicating one or more nodes;
  7. ownerDocument: points to the only document node in the document.

Manipulation node

Add node:

  1. appendChild(): append a node to the tail; Receive a parameter:

    • Nodes to be added
  2. insertBefore(): insert a node before the reference node; Receive two parameters:

    • Nodes to be added;
    • Reference node;

    If the reference node is null, the effect is the same as appendChild();

  3. replaceChild(): replace the target node; Receive two parameters:

    • Node to insert
    • Node to replace

    The replaced node will completely disappear in the DOM tree.

Delete node:

  1. removeChild(): deletes a node; Receive a parameter:

    • Node to delete

    The deleted node will be returned

Clone node:

  1. cloneNode(): clone a node and receive a parameter:

    • Boolean value indicating whether to deep clone; If true, clone the node and its children.

    Return the cloned node; If the cloned node does not specify a parent node, it is called an orphan node.

    Note that this cloning method will not clone JavaScript attributes of nodes, such as time handlers, but only copy HTML attributes.

14.1.2 Document type

Represents the document node type in JavaScript. In the browser, document is an instance of HTMLDocument, representing the whole page;

The document object is read-only, and there are generally no methods such as appendChild()

Document type has the following characteristics:

  • nodeType equals 9;
  • nodeName value is "#document";
  • nodeValue value is null;
  • parentNode value is null;
  • The ownerDocument value is null;
  • The child node can be of DocumentType (at most one), Element (at most one), ProcessingInstruction or Comment type.

Document child node:

document provides three shortcuts to access child nodes:

  1. document.documentElement: always points to < HTML > elements
  2. document.body: always point to < body > elements
  3. document.doctype: always point to <! DOCTYPE > element

document information

document provides information about the web page loaded by the browser

  1. document.title: get the content of < title > tag, which can be modified and will be rendered to the web page.
  2. document.URL: get the URL of the current page
  3. document.domain: get the domain name of the current page
  4. document. Refer: get the current page source

be careful:

  • URL and domain are related. If the URL is https://www.baidu.com/search=xxxx , then the domain name is www.baidu.com com;

  • Of the last three properties, only domain is settable. For security reasons, domain can only be set as a subset of the URL:

    // The current URL is: www.p2p.com baidu. com
    document.domain = 'p2p.baidu';	// success
    document.domain = 'baidu.com';	// success
    document.domain = 'hahaha.net';	// fail
    
  • Setting the domain is useful when a page contains panes (< frame >) or embedded panes (< iframe >) from different subdomains. Because there are potential safety hazards in cross source communication; Therefore, you can set the domain on each page to the same value to access each other's JavaScript objects.

  • Once the domain attribute is tightened, it can no longer be relaxed

    // The current URL is: www.p2p.com baidu. com
    document.domain = 'p2p.baidu.com';	// Relax and succeed
    document.domain = 'baidu.com';		// Continue to relax and succeed
    document.domain = 'p2p.baidu.com';	// Tighten again, wrong!
    

Positioning element

document provides a way to locate elements

  • getElementById(): get the node according to the id attribute. Because the id is unique, the first one found is returned.
  • getElementsByClassName(): get the node according to the class attribute.
  • getElementsByTagName(): get the node according to the tag name.
  • getElementsByName(): get the node according to the name attribute.

Except for the first method, all the others return an HTMLCollection object; This object is also a class array object.

Special set

  • document.anchors include all < a > elements with name attribute in the document;
  • document.forms contains all < form > elements in the document;
  • document.images contains all < img > elements in the document;
  • document.links contains all < a > elements with href attributes in the document.

Document writing

  • write(): input information to the document and receive a parameter: input content
  • writeln(): the former is the same, but the line feed will be added automatically

be careful:

  • These two methods can only be used in the page renderer. If they are called after rendering, the content will cover the whole page

  • When using this method to dynamically write external resources, you should pay attention to:

    <html>
    <head>
        <title>document.write() Example</title>
    </head>
    <body>
        <script type="text/javascript">
            document.write("<script type=\"text/javascript\" src=\"file.js\">" +
                "<\/script>");</script>
    </body>
    </html>
    

    The escape character must be added before the second script of the output content, otherwise there will be a matching error;

14.1.3 Element type

In addition to Document type, Element type is the most commonly used type in Web development; Elment represents HTML elements and exposes the ability to access Element tag names, child nodes and attributes.

Element type has the following characteristics:

  • nodeType equals 1;
  • nodeName value is the tag name of the element;
  • nodeValue value is null;
  • The parentNode value is a Document or Element object; Child nodes can be Element, Text, Comment, ProcessingInstruction, CDATASection types.

feature extraction

  • The nodeName or tagName attribute can obtain the tag name of the element, and in HTML, the tag name of the element is always expressed in all uppercase;

To prevent confusion of case when extracting tag names, the following operations are recommended:

if(element.tagName.toLowerCase() == 'div'){
    // do something
}

HTML Element

All HTML elements are identified by HTMLElement type, including direct instances and indirect instances;

HTMLElement directly inherits from Element and adds some standard attributes:

  • id, the unique identifier of the element in the document;
  • title, which contains additional information of elements, usually displayed in the form of prompt bar;
  • lang, language code of element content (rarely used);
  • dir, the writing direction of the language ("ltr" means from left to right, "rtl" means from right to left, which is also rarely used);
  • className, equivalent to the class attribute, is used to specify the CSS class of the element (because class is ECMAScript keyword, it cannot be used directly).

These values can be obtained and modified directly:

// <div id='myDiv' class='myClass' title='hello world'>
let div = document.getElementById('myDiv');
console.log(div.class);	// myClass
div.class = 'myLess';
console.log(div.class);	// myLess

Modifying these properties will have an impact on the page.

Operation properties

Each element has zero or more attributes. We can operate attributes in the following three ways:

  • getAttribute(): get the attribute and receive a parameter: attribute name
  • setAttribute(): sets an attribute and receives two parameters: attribute name and attribute value
  • removeAttribute(): remove the attribute and receive a parameter: attribute name

Note: the above three methods of operating attributes:

  • You can operate standard attributes (also known as recognized attributes) or user-defined attributes;
  • Property names are not case sensitive
  • In the HTML5 specification, user-defined attributes need to be prefixed with data - to facilitate verification;
  • Custom attributes will not be called DOM object attributes, that is, they will not be called recognized attributes or standard attributes;
  • You can obtain not only general attributes, but also event attributes, such as onclick;
  • If getAttribute() is used to obtain the event attribute value, the source code is returned in the form of string; Usually directly access the event attribute: element Onclick, a function is returned;
  • Developers generally use getAttribute() to access custom attributes, while recognized attributes can be accessed directly: element id

attributes attribute

The attributes attribute is similar to the childNodes attribute and is also a real-time collection similar to the NodeList. Each attribute is represented as an attrib node and saved in the NamedNodeMap object. It contains the following methods:

  • getNamedItem(name), return the node whose nodeName attribute is equal to name;
  • removeNamedItem(name), delete the node whose nodeName attribute is equal to name;
  • setNamedItem(node), add a node node to the list and take its nodeName as the index;
  • item(pos), which returns the node at the index position pos.

Generally, developers use three methods to operate attributes, and the application scenario of attributes attribute is mainly when all attributes on the element need to be iterated

function outputAttributes(element) {
    let pairs = [];
    for(let i=0 , len = element.attributes.length ; i < len ; ++i){
        const attribute = element.attributes[i];
        pairs.push(`${attribute.nodeName}=${attribute.nodeValue}`);
    }
    return pairs.join(" ");
}
//<div id='myDiv' class='myClass' title='hello world'>
let div = document.getElementById('myDiv');
console.log(outputAttributes(div)); // id=myDiv class=myClass title=hello world

Create element

Through document CreateElement (tagName) to create elements. In HTML, tag names are case insensitive;

be careful:

  • The created elements can be defined and modified through three methods of attribute operation, or directly operate recognized attributes;
  • The created element can only be inserted into the DOM tree through methods such as appendChild();
  • When the created element is added to the DOM tree, the browser will render it immediately. If the element is modified, it will also be reflected in the browser immediately.

Element offspring

An element can have any number of child elements, and the childNodes attribute contains all the direct child nodes of the element; These child nodes can be other elements, text nodes, comments or processing instructions;

Note: there are seven sub nodes under < UL >, which are three < li > and four empty Text nodes

<ul id="myList"> An empty
 An empty	<li>Item 1</li> Two empty
 Two empty	<li>Item 2</li>	Three empty
 Three empty	<li>Item 3</li> Four empty
</ul>
<ul id="myList"><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul>	<!-->Three child nodes<--!>

14.1.4 Text type

Text node, including plain text and transferred HTML characters; It has the following characteristics:

  • nodeType equals 3;
  • nodeName value is "#text";
  • nodeValue is the text contained in the node;
  • The parentNode value is the Element object;
  • Child nodes are not supported.

Text node, which can directly access text content through nodeValue or data; The number of text characters can be obtained through the length attribute; The following methods are also exposed:

  • appendData(text), add text to the end of the node;
  • deleteData(offset, count): delete count characters from position offset;
  • insertData(offset, text), insert text at position offset;
  • replaceData(offset, count, text), replace the text from position offset to offset + count with text;
  • splitText(offset), which splits the current text node into two text nodes at offset;
  • substringData(offset, count) to extract text from position offset to offset + count.

be careful:

  • If the node is in the DOM tree, if it is modified, it will be immediately reflected in the browser;

  • HTML code will be converted to entity code, i.e. less than sign, greater than sign and quotation mark will be escaped:

    // Lt & strong; / gt; other output
    div.firstChild.nodeValue = "Some <strong>other</strong> message";
    

Create text node

Through document Createtextnode(); After the text is created, its ownerDocument property will be set to document

Normalized text node

One text node is enough to express a text string. It is obviously inappropriate to have two text nodes in the element;

We can call normalize() on the parent node to merge:

let element = document.createElement('div');
let text1 = document.createTextNode('Hello,');
let text2 = document.createTextNode('world!');
element.appendChild(text1);
element.appendChild(text2);
console.log(element.length);	// 2
element.normalize();
console.log(element.length);	// 1

Split text node

Obviously, if there is a merge, there will be a split. splitText() is used to split text nodes;

It receives a parameter: where to start splitting.

14.1.5 Comment type

The DOM annotation is the Comment type; It has the following characteristics:

  • nodeType equals 8;
  • nodeName value is "#comment";
  • nodeValue is the content of the comment;
  • The parentNode value is a Document or Element object;
  • Child nodes are not supported.

It is similar to Text, but Comment is not commonly used, so I won't explain it here

14.1.6 DocumentType

DocumentType contains the information of the document and has the following characteristics:

  • nodeType equals 10;
  • The nodeName value is the name of the document type;
  • nodeValue value is null;
  • The parentNode value is the Document object;
  • Child nodes are not supported.

DocumentType node, generally only the name attribute is useful, which contains the name of the document type;

14.1.7 DocumentFragment type

Of all node types, DocumentFragment type is the only type that does not have a representation for in the tag. DOM defines document fragments as lightweight documents, which can contain and operate nodes without additional consumption of complete documents; It has the following characteristics:

  • nodeType equals 10;
  • The nodeName value is the name of the document type;
  • nodeValue value is null;
  • The parentNode value is the Document object;
  • Child nodes are not supported.

Usefulness of document fragments

Document fragments act as repositories for other nodes added to the document tree.

Because every time a node is added to the DOM tree, the DOM tree will be re rendered. If there are multiple addition operations, it will be re rendered many times, reducing performance and efficiency;

We can add the nodes that need to be added to the document fragment first, and then add the document fragment to the DOM tree. In this way, we only need to render it again, and the document fragment itself will not be rendered to the DOM tree;

We can use createDocumentFragment() to create document fragments;

14.1.8 Attr type

The attributes attribute is a node of type attrib, which has the following characteristics:

  • nodeType equals 2;
  • nodeName value is the attribute name;
  • nodeValue is the attribute value;
  • parentNode value is null;
  • Child nodes are not supported in HTML;
  • In XML, the child node can be Text or EntityReference.

Although Attr node is also a node of DOM tree, it is rarely used because developers like to use getAttribute and other methods;

14.2 DOM programming

DOM operation can be done in HTML or JavaScript;

14.2.1 dynamic script

Insert a script by inserting a < script > element into the web page.

14.2.2 dynamic style

Similar to 14.2.1, the style is inserted by inserting < style > elements into the web page.

Whether it is dynamic loading script or dynamic loading style; Is an asynchronous process.

14.2.3 operation form

Table is a complex structure in HTML,

<table border="1" width="100%">
    <tbody>
        <tr>
            <td>Cell 1,1</td>
            <td>Cell 2,1</td>
        </tr>
        <tr>
            <td>Cell 1,2</td>
            <td>Cell 2,2</td>
        </tr>
    </tbody>
</table>

It is troublesome to create tables through DOM;

// Create table
let table = document.createElement("table");
table.border = 1;
table.width = "100%";
// Create table body
let tbody = document.createElement("tbody");
table.appendChild(tbody);
// Create first row
let row1 = document.createElement("tr");
tbody.appendChild(row1);
let cell1_1 = document.createElement("td");
cell1_1.appendChild(document.createTextNode("Cell 1,1"));
row1.appendChild(cell1_1);
let cell2_1 = document.createElement("td");
cell2_1.appendChild(document.createTextNode("Cell 2,1"));
row1.appendChild(cell2_1);
// Create second row
let row2 = document.createElement("tr");
tbody.appendChild(row2);
let cell1_2 = document.createElement("td");
cell1_2.appendChild(document.createTextNode("Cell 1,2"));
row2.appendChild(cell1_2);
let cell2_2= document.createElement("td");
cell2_2.appendChild(document.createTextNode("Cell 2,2"));
row2.appendChild(cell2_2);
// Add table to body of document
document.body.appendChild(table);

However, DOM adds some attributes and methods to < Table >, < tbody >, < tr > elements to improve the efficiency of creation:

The < Table > element adds the following attributes and methods:

  • Caption, pointer to < caption > element (if any);
  • tBodies, HTMLCollection containing < tbody > elements;
  • TFoot, pointing to the < tFoot > element (if present);
  • tHead, pointing to the < tHead > element (if present);
  • Rows, including HTMLCollection representing all rows;
  • Createhead(), create the < thead > element, put it into the table and return the reference; createTFoot(), create the < tFoot > element, put it in the table and return the reference;
  • createCaption(), create the < caption > element, put it in the table and return the reference;
  • Deletethread(), delete the < thead > element;
  • deleteTFoot(), delete the < tFoot > element;
  • deleteCaption(), delete the < caption > element;
  • deleteRow(pos), delete the row at a given position;
  • insertRow(pos), inserts a row at a given position in the row set.

The < tbody > element adds the following attributes and methods:

  • Rows, HTMLCollection containing all rows in the < tbody > element;
  • deleteRow(pos), delete the row at a given position;
  • insertRow(pos), which inserts a row at a given position in the row set and returns the reference of the row.

The < tr > element adds the following attributes and methods:

  • cells, HTMLCollection containing all table elements of < tr > element;
  • deleteCell(pos) to delete the table element at a given location;
  • insertCell(pos) inserts a table element at a given position in the table element set and returns the reference of the table element.

After rewriting through methods and attributes:

// Create table
let table = document.createElement("table");
table.border = 1;
table.width = "100%";
// Create table body
let tbody = document.createElement("tbody");
table.appendChild(tbody);
// Create first row
tbody.insertRow(0);
tbody.rows[0].insertCell(0);
tbody.rows[0].cells[0].appendChild(document.createTextNode("Cell 1,1"));
tbody.rows[0].insertCell(1);
tbody.rows[0].cells[1].appendChild(document.createTextNode("Cell 2,1"));
// Create second row
tbody.insertRow(1);
tbody.rows[1].insertCell(0);
tbody.rows[1].cells[0].appendChild(document.createTextNode("Cell 1,2"));
tbody.rows[1].insertCell(1);
tbody.rows[1].cells[1].appendChild(document.createTextNode("Cell 2,2"));
// Add table to body of document
document.body.appendChild(table);

14.2.4 using NodeList

NodeList objects, related NameNodeMap and HTMLCollection are * * "real-time" collections! This means that the changes of document structure will be reflected in them in real time * *;

Don't do this: because it will cycle indefinitely!

let divs = document.getElementsByTagNmae('div');
for(let i = 0 ; i < divs.length ; i++ ) {
    let div = document.createElement('div');
    document.body.appendChild(div);
}

Because each query will search the entire document, it is best to limit the number of operations of NodeList, or you can choose to cache NodeList for use;

14.3 MutationObserver interface

The MutationObserver interface can asynchronously execute the callback function when the DOM is modified;

So that the MutationObserver can observe the whole file, part of the DOM tree, an element, or even an attribute, child node, text, etc;

14.3.1 basic usage

  1. Create instance:

The MutationObserver instance needs to be created through the constructor, and a callback execution function needs to be passed in;

let observer = new MutationObserver( () => {
    console.log('something changed!');
})

The newly created MutationObserver instance is not associated with DOM objects, but needs to be associated through the observe() method;

It receives two required parameters

  • DOM object to observe
  • MutationObserverInit object
let observer = new MutationObserver( () => {
    console.log('attribute was changed!');
})

observer.observe(document.body, { attributes:true } );	// It means to observe the attribute change of body

document.body.className = 'foo';
console.log('Changed body class');

// Changed body class
// attribute was changed!

In the above example, the descendant of body or other non attribute modification will not trigger the callback;

Also, callbacks are not synchronized with actual DOM changes!

  1. Callback function:

Each callback will receive an array of MutationRecord instances; The instance array contains:

  • What has changed
  • Some parts of DOM have been affected
let observer = new MutationObserver( (mutationRecords) => {
    console.log(mutationRecords);
})
observer.observe(document.body,{ attributes:true });

document.body.setAttribute('foo','bar');
/* Here is an element of a mutationRecords instance
addedNodes: NodeList []
attributeName: "foo"
attributeNamespace: null
nextSibling: null
oldValue: null
previousSibling: null
removedNodes: NodeList []
target: body
type: "attributes"
*/

Before the callback is executed, multiple events that meet the observation conditions may occur, so the array of MutationRecord instances will be arranged in the queue order.

The properties of the MutationRecords instance are as follows:

attributeexplain
targetTarget node affected by modification
typeString indicating the type of change: "attributes", "characterData" or "childList"
oldValueIf enabled in the MutationObserverInit object (attributeOldValue or characterData OldValue is true), the change event of "attributes" or "characterData" will set this attribute to the replaced value. For changes of type "childList", this attribute will always be set to null
attributeNameFor "attributes" type changes, the name of the modified attribute is saved here. Other change events will set this attribute to null
attributeNamespaceFor the change of "attributes" type using namespace, the name of the modified attribute is saved here. Other change events will set this attribute to null
addedNodesFor the change of "childList" type, return the NodeList containing the nodes added in the change. The default is empty NodeList
removedNodesFor the change of "childList" type, return the NodeList containing the nodes deleted in the change. The default is empty NodeList
previousSiblingFor the change of "childList" type, the previous sibling Node of the change Node is returned, which is null by default
nextSiblingFor changes of "childList" type, the next sibling Node of the returned change Node is null by default

The callback will also receive the second parameter: the instance of MutationObserver observing the change:

let observer = new MutationObserver( (mutationRecords,mutationObserver) => {
    console.log(mutationRecords);
    console.log(mutationObserver);		// MutationObserver {}
})
observer.observe(document.body,{ attributes:true });

document.body.setAttribute('foo','bar');
  1. Cancel observation

By default, as long as the observed object is not garbage collected, the callback of MutationObserver will respond to DOM change events;

If you need to terminate the execution in advance, you can use the disconnect() method; This method will not only stop the observation, but also discard the callback that has been added to the task queue to be executed asynchronously;

let observer = new MutationObserver( (mutationRecords,mutationObserver) => {
    console.log(mutationRecords);
})
observer.observe(document.body,{ attributes:true });

document.body.setAttribute('foo','bar');
observer.disconnect();	// After disconnection, the console has no output;

If you need to stop observation and don't want to abandon the callback that has entered the task queue; You can do this:

let observer = new MutationObserver( (mutationRecords,mutationObserver) => {
    console.log(mutationRecords);
})
observer.observe(document.body,{ attributes:true });

document.body.setAttribute('foo','bar');
setTimeout(()=>{
    observer.disconnect();
},0);	// The link is broken, but the console has an output

The above code changes the disconnect() that should be executed synchronously into the code that is executed asynchronously, and also adds it to the task queue. You need to wait for the callback in front of the queue to execute before executing disconnect()

  1. Reuse MutationObserver instance

By calling the observe() method multiple times, you can reuse the same MutationObserver instance and observe multiple different target nodes;

At this time, the target attribute of MutationRecord can identify the target node of the change event;

let observer = new MutationObserver( (mutationRecords,mutationObserver) => {
    console.log(mutationRecords.map( (item) => {
        return item.target
    }));
})

let childA = document.createElement('div');
let childB = document.createElement('span');

document.body.appendChild(childA);
document.body.appendChild(childB);

// Reuse observer
observer.observe(childA , { attributes:true });
observer.observe(childB , { attributes:true });

childA.setAttribute('foo','bar');
childB.setAttribute('foo','bar');
// output
// [div, span]
  1. Reuse the MutationObserver instance

Calling disconnect() does not end the life of the MutationObserver. You can reuse the observer.

14.3.2 observation range of mutationobserveinit

The MutationObserveInit object controls the range of observation by means of key pair values. It has the following properties:

attributeexplain
subtreeBoolean value, indicating whether to observe the subtree (descendants) of the target node except the target node. If false, only the changes of the target node will be observed; If true, the default value of the observation target node and its entire subtree is false
attributesBoolean value, indicating whether to observe the attribute change of the target node. The default is false
attributeFilterString array, indicating which attributes to observe. Setting this value to true will also convert the value of attributes to true. By default, all attributes are observed
attributeOldValueBoolean value, indicating whether the MutationRecord records the attribute value before the change. Setting this value to true will also convert the value of attributes to true, which is false by default
characterDataBoolean value indicating whether the change event is triggered when modifying character data. The default value is false
characterDataOldValueBoolean value, indicating whether the MutationRecord records the character data before the change. Setting this value to true will also convert the value of characterData to true, which is assumed to be false by default
childListBoolean value, indicating whether the child node of the modified target node triggers a change event. The default value is false

Note: when calling observe(), there must be a value in the MutationObserveInit object, otherwise an error will be reported because there is no change event to trigger the callback!

  1. Observation attribute

    By setting attribute:true, you can observe the addition, deletion and modification of attributes:

    let observer = new MutationObserver( (mutationRecords,mutationObserver) => {
        console.log(mutationRecords);
    })
    observer.observe(document.body,{ attributes:true });
    
    document.body.setAttribute('foo','bar');
    

    If you only want to observe some values, you can set the white list through the attributeFilter attribute;

    let observer = new MutationObserver( (mutationRecords,mutationObserver) => {
        console.log(mutationRecords);
    })
    observer.observe(document.body,{ attributeFilter:['foo','title'] });
    
    document.body.setAttribute('foo','bar');	// A callback is triggered
    document.body.setAttribute('title','this is a body');	// A callback is triggered
    document.body.setAttribute('class','bodyClass');	// No callback will be triggered
    

    The above operations have no memory, that is, we don't know what the previous value was before the change;

    If you want to get the old value, you can set attributeOldValue:true.

    let observer = new MutationObserver( (mutationRecords,mutationObserver) => {
        console.log(mutationRecords.map( (item) =>{
            return item.oldValue;
        }));
    })
    observer.observe(document.body,{ attributeOldValue:true });
    
    document.body.setAttribute('title','hello');	
    document.body.setAttribute('title','world');
    document.body.setAttribute('title','!');	
    
    // Console output: [null, 'hello', 'world']
    
  2. Observe character data

    You can observe text nodes by setting characterDate:true

    Similar to the observation attribute, the old value can also be obtained by setting;

  3. Observe child nodes

    By setting childList:true;

    It should be noted that reordering the child nodes will trigger two event changes, one is to remove and the other is to add;

  4. Observation subtree

    By setting subtree:true;

    // Empty body
    document.body.innerHTML = '';let observer = new MutationObserver(
    (mutationRecords) => console.log(mutationRecords));
    // Create a descendant
    document.body.appendChild(document.createElement('div'));
    // Observe the < body > element and its subtree
    observer.observe(document.body, { attributes: true, subtree: true });
    // Modify the subtree of < body > element
    document.body.firstChild.setAttribute('foo', 'bar');
    // The event of subtree change is recorded
    // [
    // {
    // addedNodes: NodeList[],
    // attributeName: "foo",
    // attributeNamespace: null,
    // oldValue: null,
    // nextSibling: null,
    // previousSibling: null,
    // removedNodes: NodeList[],
    // target: div,
    // type: "attributes",
    // }
    // ]
    

    Interestingly, the nodes of the observed subtree can still trigger change events after removing the subtree

    // Empty body
    document.body.innerHTML = '';
    let observer = new MutationObserver(
    	(mutationRecords) => console.log(mutationRecords)
    );
    let subtreeRoot = document.createElement('div'),
    subtreeLeaf = document.createElement('span');
    // Create a subtree with two levels
    document.body.appendChild(subtreeRoot);
    subtreeRoot.appendChild(subtreeLeaf);
    // Observation subtree
    observer.observe(subtreeRoot, { attributes: true, subtree: true });
    // Transfer nodes to other subtrees
    document.body.insertBefore(subtreeLeaf, subtreeRoot);
    subtreeLeaf.setAttribute('foo', 'bar');
    // The removed node still triggers the change event
    // [MutationRecord]
    

14.3.3 asynchronous callback and record queue

The MutationObserver interface is designed for performance. Its core is asynchronous callback and record queue model.

In order not to affect performance when a large number of change events occur. Each change of information (* information set by the MutationObserverInit object) * will be saved in the MutationRecord instance and then added to the record queue.

This queue is unique to each MutationRecord instance and is an ordered list of DOM change events.

  1. Record queue

    Every time the MutationRecord is added to the record queue of the MutationObserver, the callback registered by the observer (passed in when initializing the MutationObserver) will be scheduled to the task queue as a micro task only if there is no scheduled micro task callback before * (the length of the micro task in the queue is 0) *. This ensures that the contents of the record queue will not be processed twice by the callback.

    However, during the asynchronous execution of the callback micro task, more change events may be triggered. Therefore, the called callback will receive an array of MutationRecord instances in the order they enter the giant deer queue. The callback needs to handle the instance array, because these instances will not exist after the function exits.

  2. takeRecords() method

    Call the takeRecords() method of the MutationObserver instance to clear the record queue, take out and return all the MutationRecord instances.

    let observer = new MutationObserver(
    	(mutationRecords) => console.log(mutationRecords)
    );
    observer.observe(document.body, { attributes: true });
    document.body.className = 'foo';
    document.body.className = 'bar';
    2document.body.className = 'baz';
    console.log(observer.takeRecords());
    console.log(observer.takeRecords());
    // [MutationRecord, MutationRecord, MutationRecord]
    // []
    

    *This is especially useful when you want to stop observing (disconnect()) * and also need to deal with the MutationRecord instance in the record queue discarded by disconnect().

14.3.4 performance, memory and garbage collection

Delegating the change callback to the micro task can ensure the synchronous triggering of events and avoid the subsequent confusion. The record queue implemented for MutationObserver can ensure that even if the change event is triggered explosively, it will not significantly slow down the browser.

There are also costs when using MutationObserver, so you need to pay attention to the following points:

  1. The reference relationship between the MutationObserver instance and the target node is asymmetric. MutationObserver has a weak reference to the target node to be observed. Because it is a weak reference, it does not prevent the garbage collector from collecting the target node.

    However, the target node has a strong reference to MutationObserver. If the target node is removed from the DOM and then garbage collected, the closed MutationObserver will also be garbage collected

  2. Each MutationRecord instance in the record queue contains at least one reference to an existing DOM node. If the change is of childList type, it will contain references to multiple nodes. The default behavior of the record queue and callback processing is to exhaust the queue, process each MutationRecord, and then make them out of scope and garbage collected.

    Sometimes it may be necessary to keep a complete change record of an observer. Saving these MutationRecord instances will also save the nodes they refer to, which will prevent these nodes from being recycled.

    If you need to free up memory as soon as possible, it is recommended to extract the most useful information from each MutationRecord, save it to a new object, and finally discard the MutationRecord.

Keywords: Javascript Front-end

Added by digitalhuman on Sun, 27 Feb 2022 16:10:17 +0200