The evolution of the DOM

The DOM2 and DOM3 Core modules aim to extend the DOM API to meet all the requirements of XML and provide better error handling and feature detection.

XML namespaces (Understanding)

XML namespaces make it possible to mix different XML languages in a well-formed document without worrying about element naming conflicts. Strictly speaking, XML namespaces are supported in XHTML, not HTML. As the understanding

Other changes

The change of the DocumentType

DocumentType has three new attributes: publicId, systemId, and internalSubset.

The publicId and systemId properties represent data that is valid in the document type declaration but cannot be accessed using DOM1 API.

<! DOCTYPEhtml PUBLIC "-// DTD XHTML 1.0 Strict// EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" 
[<! ELEMENTname (#PCDATA) >] >
Copy the code

PublicId is “-// W3C// DTD XHTML 1.0 Strict// EN”,

SystemId is “www.w3.org/TR/xhtml1/D…

InternalSubset is “<! ELEMENT name (# PCDATA) > “.

There is usually little need to access this information in a web page.

The change of the Document

The purpose of the importNode() method is to take a node from another document and import it into the new document so that it can be inserted into the new document.

The importNode() method is similar to the cloneNode() method in that it takes two arguments: the node to copy and a Boolean value indicating whether to copy the subtree at the same time, and returns the new node suitable for use in the current document.

let newNode = document.importNode(oldNode, true); // Import nodes and all descendants
document.body.appendChild(newNode);
Copy the code

This approach is not used much in HTML, but more often in XML documents

DOM2 View adds a new property to the Document type defaultView, which is a pointer to the window (or pane) that owns the current Document. IE8 and earlier versions support the equivalent parentWindow property, which is also supported by Opera. So to determine which window has the document, use the following code:

let parentWindow = document.defaultView || document.parentWindow;
Copy the code

DOM2 Core also adds two new methods for document.implementation objects: createDocumentType() and createDocument().

The former is used to create a new node of type DocumentType and receives three parameters: DocumentType name, publicId, and systemId.

For example, the following code can create a new HTML 4.01 strict document:

Let a doctype = document. Implementation. CreateDocumentType (" HTML ", "- / / / / W3C DTD HTML 4.01 / / EN", "http://www.w3.org/TR/html4/strict.dtd");Copy the code

CreateDocument () takes three parameters: the namespaceURI of the document element, the label name of the document element, and the document type.

For example, the following code can create an empty XML document:

let doc = document.implementation.createDocument(""."root".null);
Copy the code

To create an XHTML document, use the following code:

let doctype = document.implementation.createDocumentType("html"."-// DTD XHTML 1.0 Strict// EN"."http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"); 
let doc = document.implementation.createDocument("http://www.w3.org/1999/xhtml"."html", doctype);
Copy the code

Here, a new XHTML document is created using the appropriate namespace and document type. This document has only one document element; everything else needs to be added separately.

The DOM2 HTML module also adds the createHTMLDocument() method to the Document.implamentation object. You can use this method to create a complete HTML document with,,, and elements. This method takes a single parameter, the title of the newly created document (placed in the element), and returns a new HTML document.

Such as:

let htmldoc = document.implementation.createHTMLDocument("New Doc"); 
console.log(htmldoc.title); // "New Doc" 
console.log(typeof htmldoc.body); // "object"
Copy the code

The createHTMLDocument() method creates an object that is an instance of the HTMLDocument type and therefore contains all methods and attributes associated with that type, including the title and body attributes.

The change of the Node

DOM3 has two new methods for comparing nodes: isSameNode() and isEqualNode().

Both methods take a node argument and return true if the node is the same or equal to the reference node. The same nodes refer to the same object. Nodes are equal, which means that the nodes are of the same type, have the same attributes (nodeName, nodeValue, etc.), and the attributes and childNodes are equal (that is, the same location contains the same values).

Here’s an example:

let div1 = document.createElement("div"); 
div1.setAttribute("class"."box"); 
let div2 = document.createElement("div");
div2.setAttribute("class"."box");
console.log(div1.isSameNode(div1)); // true 
console.log(div1.isEqualNode(div2)); // true 
console.log(div1.isSameNode(div2)); // false
Copy the code

Two that contain the same attribute are created here

Elements. These two elements are equal, but not identical.

DOM3 also adds methods for attaching extra data to DOM nodes. The setUserData() method takes three arguments: a key, a value, and a handler function to append data to the node.

Data can be added to a node as follows:

document.body.setUserData("name"."Nicholas".function() {});
Copy the code

The same key can then be used to retrieve this information, for example:

let value = document.body.getUserData("name");
Copy the code

The setUserData() handler is executed when the node containing the data is copied, deleted, renamed, or imported into another document, and you can decide what to do with the user data at this point. The processing function takes five arguments: a numeric value for the type of operation (1 for copy, 2 for import, 3 for delete, and 4 for rename), the key of the data, the value of the data, and the source and destination nodes. When a node is deleted, the source node is null. The target node is null except for replication.

let div = document.createElement("div"); 
div.setUserData("name"."Nicholas".function(operation, key, value, src, dest) { 
 if (operation == 1) { 
 dest.setUserData(key, value, function() {}); }});let newDiv = div.cloneNode(true); 
console.log(newDiv.getUserData("name")); // "Nicholas"
Copy the code

I’m going to create one here

Element, and then add some data to it, including the user’s name. When the element is copied using cloneNode(), the handler function is called to append the same data to the copied target node. A call to getUserData() on the replica node then retrieves the data attached to the source node.

Changes to the inline pane

DOM2 HTML adds a property to the HTMLIFrameElement (i.e., inline pane) type called contentDocument. This property contains a pointer to the Document object that represents the contents of the child inline pane. The following example shows how to use this property:

let iframe = document.getElementById("myIframe"); 
let iframeDoc = iframe.contentDocument;
Copy the code

The contentDocument property is an instance of Document and has all the Document properties and methods, so it can be used just like any other HTML Document. There is also a property, contentWindow, that returns the window object of the corresponding pane, which has a Document property on it. All modern browsers support the contentDocument and contentWindow properties.

style

Styles in HTML can be defined in three ways: external style sheets (through elements), document style sheets (using elements), and element-specific styles (using the style attribute).

Access element style

Any HTML element that supports the style attribute will have a corresponding style attribute in JavaScript. This style attribute is an instance of the CSSStyleDeclaration type, which contains all style information set for the element through the HTML style attribute, but not styles inherited from the document style and external styles through the cascading mechanism.

Because CSS property names are hyphenated, they must be converted to camel case in JavaScript.

CSS properties JavaScript attribute
background-image style.backgroundImage
color style.color
display style.display
font-family style.fontFamily

Most attribute names are directly converted this way. But one CSS property name that you can’t convert directly is float. Because float is a JavaScript reserved word, it cannot be used as a property name. DOM2 Style specifies that its corresponding property in the Style object should be cssFloat.

Any time you get a reference to a valid DOM element, you can style it through JavaScript. Consider the following example:

let myDiv = document.getElementById("myDiv"); 
// Set the background color
myDiv.style.backgroundColor = "red"; 
// Change the size
myDiv.style.width = "100px"; 
myDiv.style.height = "200px"; 
// Set the border
myDiv.style.border = "1px solid black";
Copy the code

When you modify the style like this, the appearance of the element is automatically updated.

Values set through the style property can also be obtained through the style object. For example, the following HTML:

<div id="myDiv" style="background-color: blue; width: 10px; height: 25px"></div>
Copy the code

The value of the element’s style attribute can be obtained from code like this:

console.log(myDiv.style.backgroundColor); // "blue" 
console.log(myDiv.style.width); // "10px" 
console.log(myDiv.style.height); // "25px"
Copy the code

If there is no style attribute on the element, the Style object contains null values for all possible CSS attributes.

DOM style properties and methods

  • CssText, which contains the CSS code in the style property.
  • Length, the number of CSS attributes applied to the element.
  • ParentRule, the CSSRule object that represents CSS information (the CSSRule type is discussed in the next section).
  • getPropertyCSSValue(propertyName), returns containing CSS propertiespropertyNameValue of CSSValue object (deprecated).
  • getPropertyPriority(propertyName), if the CSS propertypropertyNameUse! Important returns “important”, otherwise an empty string.
  • getPropertyValue(propertyName), returns the propertypropertyNameThe string value of.
  • item(index), returns an index ofindexThe name of the CSS property.
  • removeProperty(propertyName), remove the CSS property from the stylepropertyName.
  • setProperty(propertyName, value, priority), set the CSS propertiespropertyNameThe value ofvalue.priorityIs “important” or an empty string.

If an element has a border set through the style attribute, and the value assigned to the cssText attribute does not contain the border, the element’s border disappears. The following example demonstrates the use of cssText:

myDiv.style.cssText = "width: 25px; height: 100px; background-color: green"; 
console.log(myDiv.style.cssText);
Copy the code

Setting cssText is the fastest way to change more than one style of an element at once, because all changes take effect at the same time.

Calculate the style

The Style object contains the style information that the element that supports the style property sets for that property, but not the cascading style information from other stylesheets that also affects the element.

DOM2 Style adds the getComputedStyle() method to document.defaultView. This method takes two arguments: the element and the pseudo-element string to get the computed style;

The second argument can be passed null if no pseudo-element query is required. The getComputedStyle() method returns a CSSStyleDeclaration object (of the same type as the style property) containing the element’s computed style.

Suppose you have the following HTML page:

<! DOCTYPEhtml> 
<html> 
<head> 
   <title>Computed Styles Example</title> 
   <style type="text/css"> 
     #myDiv { 
     background-color: blue; 
     width: 100px; 
     height: 200px; 
     } 
 	</style> 	
</head> 
<body> 
 	<div id="myDiv" style="background-color: red; border: 1px solid black"></div> 
</body> 
</html>
Copy the code

The following code gets the computed style from this element:

let myDiv = document.getElementById("myDiv"); 
let computedStyle = document.defaultView.getComputedStyle(myDiv, null); 
console.log(computedStyle.backgroundColor); // "red" 
console.log(computedStyle.width); // "100px" 
console.log(computedStyle.height); // "200px" 
console.log(computedStyle.border); // "1px solid black" (in some browsers)
Copy the code

When you get the computed style for this element, the resulting background color is “red”, the width is “100px”, and the height is “200px”. The background color is not “blue” because the element style overrides it.

The border property does not necessarily return the actual border rule in the stylesheet (some browsers do). This inconsistency is caused by the way the browser interprets the shorthand style, such as border, which actually sets a group of attributes. When setting a border, you actually set the line width, color, and style of the four edges (border-left-width, border-top-color, border-bottom-style, etc.). . Therefore, even if computedStyle border in all browsers will not return value computedStyle. BorderLeftWidth return value will be

Note that the browser may return style values, but not necessarily in the same format. For example, Firefox and Safari convert all color values to RGB (red becomes RGB (255,0,0)), while Opera converts all colors to hexadecimal (red becomes #ff0000). So be sure to test more than one browser when using getComputedStyle().

One thing to remember about computing styles is that computing styles are read-only in all browsers and you cannot modify the object returned by the getComputedStyle() method. Furthermore, the computed style also contains information from the browser’s internal style sheets. So CSS properties with default values will appear in the computed style.

Action style sheet

The CSSStyleSheet type represents CSS stylesheets, both using elements and stylesheets defined by elements. Notice that the two elements themselves are HTMLLinkElement and HTMLStyleElement. The CSSStyleSheet type is a generic stylesheet type that can represent stylesheets defined in HTML in any way. In addition, the element-specific type allows HTML attributes to be modified, whereas an instance of the CSSStyleSheet type is a read-only object (except for one attribute).

The CSSStyleSheet type inherits StyleSheet, which can be used as a base class for non-CSS stylesheets. The following are the attributes that CSSStyleSheet inherits from StyleSheet

  • Disabled, a Boolean value that indicates whether the stylesheet is disabled (this property is read-write, so setting it to true disables the stylesheet).
  • Href, returns the URL of the stylesheet if an contained stylesheet is used, or null otherwise.
  • Media, a collection of media types supported by the stylesheet, which has a length attribute and an item() method, like all DOM collections. As with all DOM collections, you can also use brackets to access specific items in the collection. Returns an empty list if the stylesheet is available for all media.
  • OwnerNode, which points to the node that owns the current stylesheet, is either an element or an element in HTML (or a processing instruction in XML). If the current stylesheet is contained in another stylesheet via @import, this property is null.
  • ParentStyleSheet, which points to the stylesheet that imported it if the current stylesheet was included in another stylesheet via @import.
  • Title, the title property of the ownerNode.
  • Type, a string that represents the type of the stylesheet. For CSS stylesheets, it’s “text/ CSS “.
  • CssRules, the collection of style rules contained in the current style sheet.
  • OwnerRule, which points to the import rule if the stylesheet was imported using @import; Otherwise null.
  • deleteRule(index), delete the rule in cssRules at the specified location.
  • insertRule(rule.index), inserts rules into cssRules at the specified location.
let sheet = null; 
for (let i = 0, len = document.styleSheets.length; i < len; i++) { 
 sheet = document.styleSheets[i]; 
 console.log(sheet.href); 
}
Copy the code

The code above prints the href attribute for each stylesheet in the document (the element does not have this attribute)

CSS rules

The CSSRule type represents a rule in a stylesheet. This type is also a common base class, and many types inherit from it, but the most common is CSSStyleRule, which represents style information. Here are the properties available on CSSStyleRule objects.

  • CssText, returns the text of the entire rule. The text here may not be the same as the actual text in the stylesheet, because the browser handles the stylesheet differently internally. Safari always converts all letters to lowercase.
  • ParentRule, if this rule is included by another rule (such as @media), points to the include rule, otherwise null
  • ParentStyleSheet, which contains the stylesheet for the current rule.
  • SelectorText, which returns the selectorText of the rule. The text here may not be the same as the actual text in the stylesheet, because the browser handles the stylesheet differently internally. This property is read-only in Firefox, Safari, Chrome, and Internet Explorer, and can be changed in Opera.
  • Style, which returns a CSSStyleDeclaration object that can set and get the style in the current rule.
  • Type, a numeric constant, represents the rule type. For style rules, it is always 1.

Of these attributes, the most used are cssText, selectorText, and Style.

In most cases, using the style attribute is enough to accomplish the task of manipulating style rules. This object, like the Style object on each element, can be used to read or modify the style of the rule. For example, here’s a CSS rule:

div.box { 
   background-color: blue; 
   width: 100px; 
   height: 200px; 
}
Copy the code

Assuming that this rule is in the first stylesheet on the page and is the only CSS rule in the stylesheet, the following code can get all of its information:

let sheet = document.styleSheets[0]; 
let rules = sheet.cssRules || sheet.rules; // get the set of rules
let rule = rules[0]; // get the first rule
console.log(rule.selectorText); // "div.box" 
console.log(rule.style.cssText); // Complete CSS code
console.log(rule.style.backgroundColor); // "blue" 
console.log(rule.style.width); // "100px" 
console.log(rule.style.height); // "200px"
Copy the code

Using these interfaces, you can determine the style information for a style rule in the same way you determine the style contained in the element style object. As with the scene of the element, you can modify the styles in the rule as follows:

let sheet = document.styleSheets[0]; 
let rules = sheet.cssRules || sheet.rules; // get the set of rules
let rule = rules[0]; // get the first rule
rule.style.backgroundColor = "red"
Copy the code

Note that changing the rule in this way affects all elements on the page to which the rule is applied. If there are two on the page

The element has the “box” class, so both elements are affected by this change.

Create rules

DOM states that you can use the insertRule() method to add new rules to your stylesheet. This method takes two parameters: the text of the rule and an index value representing the insertion position. Here’s an example:

sheet.insertRule("body { background-color: silver }".0); // Use the DOM method
Copy the code

This example inserts a rule that changes the background color of the document. This rule is inserted as the first rule in the stylesheet (position 0), and order is important for rule stacking.

Delete rules

The DOM method that supports deleting rules from a stylesheet is deleteRule(), which takes one parameter: the index of the rule to delete. To remove the first rule in the stylesheet, do this:

sheet.deleteRule(0); // Use the DOM method
Copy the code

Like adding rules, deleting rules is not a common practice in Web development. Caution when deleting rules because it may affect CSS layering.

The element size

The offset dimension

OffsetHeight, the pixel size that the element occupies vertically, including its height, the height of the horizontal scroll bar (if visible), and the height of the top and bottom borders.

OffsetLeft, the distance outside the left border of the element contains the number of pixels inside the left border of the element.

OffsetTop, the distance outside the upper border of the element contains the number of pixels inside the upper border of the element.

OffsetWidth, the horizontal pixel size of the element, including its width, vertical scroll bar width (if visible), and the width of the left and right borders.

function getElementLeft(element) { 
 let actualLeft = element.offsetLeft; 
 let current = element.offsetParent; 
 while(current ! = =null) { 
 actualLeft += current.offsetLeft; 
 current = current.offsetParent; 
 } 
 return actualLeft; 
}

function getElementTop(element) { 
 let actualTop = element.offsetTop; 
 let current = element.offsetParent; 
 while(current ! = =null) { 
 actualTop += current.offsetTop; 
 current = current.offsetParent; 
 } 
 return actualTop; 
}
Copy the code

These two functions use offsetParent to move up the DOM tree, adding the offset attributes at each level to get the actual offset of the element.

Client size

The element’s client dimensions include the space occupied by the element’s content and its inner margins. The client size has only two related properties: clientWidth and clientHeight. Where, clientWidth is the width of the content area plus the width of the left and right inner margins, clientHeight is the height of the content area plus the height of the lower inner margins.

The client dimension is essentially the space inside the element, and therefore does not include the space occupied by the scroll bar. These two properties are most commonly used to determine the browser viewport size by detecting the clientWidth and clientHeight of document.documentElement. These two attributes represent the size of the viewport (or element).

Note: Like the offset size, the client size is read-only and recalculated on each access.

Scroll to size

The final set of dimensions, called scroll dimensions, provides information about how far an element’s content can be rolled. Some elements, for example, scroll automatically without any code, while others need to scroll using the OVERFLOW property of CSS. There are four properties related to scroll size.

  •  scrollHeight, the total height of the element’s content without the scrollbar.
  •  scrollLeft, the number of pixels hidden to the left of the content area. Setting this property changes the scrolling position of the element.
  •  scrollTop, the number of pixels hidden at the top of the content area. Setting this property changes the scrolling position of an element.
  •  scrollWidth, the total width of the element’s content without the scrollbar.

ScrollWidth and scrollHeight can be used to determine the actual size of a given element’s content. For example, the element is the element of the scrolling viewport in the browser. Therefore, the document. The documentElement. ScrollHeight total height of vertical direction is the whole page.

The relationship between scrollWidth and scrollHeight and clientWidth and clientHeight is indistinguishable on documents that do not require scrolling. If the document size exceeds the viewport size, the two pairs of properties are not equal in all major browsers, with scrollWidth and scollHeight equal to the width of the document content and clientWidth and clientHeight equal to the viewport size.

The scrollLeft and scrollTop properties can be used to determine where the current elements scroll, or to set where they scroll. When the element is unrolled, both of these attributes are equal to 0. If the element is scrolling vertically, scrollTop is greater than 0, indicating the height of the invisible region at the top of the element. If the element is scrolling horizontally, the scrollLeft is greater than 0, representing the width of the invisible area to the left of the element. Because these two attributes are also writable, setting them both to 0 resets the element’s scroll position

The following function checks if the element is at the top and scrolls it back to the top if not:

function scrollToTop(element) { 
   if(element.scrollTop ! =0) { 
   		element.scrollTop = 0; }}Copy the code

This function uses scrollTop to get and set values.

Determining element size

The browser exposes the getBoundingClientRect() method on each element, which returns a DOMRect object with six attributes: left, top, right, bottom, height, and Width. These attributes give the element’s position on the page relative to the viewport.

traverse

The DOM2 Traversal and Range module defines two types to aid sequential Traversal of DOM structures. These two types — NodeIterator and TreeWalker — perform depth-first traversal of the DOM structure from a starting point.

NodeIterator

NodeIterator type is relatively simple in two types, you can through the document. The createNodeIterator () method to create its instance. This method takes the following four parameters.

  •  root, which is used to traverse the root node.
  •  whatToShow, numeric code, which nodes should be accessed.
  •  filter, a NodeFilter object or function that indicates whether a particular node is received or skipped.
  •  entityReferenceExpansion, Boolean value indicating whether to extend entity references. This parameter has no effect in HTML documents because entity references are never extended.

The whatToShow parameter is a bitmask that specifies which nodes to access by applying one or more filters. The constant corresponding to this parameter is defined in the NodeFilter type.

  •  nodefilter. SHOW_ALL, all nodes.
  •  nodefilter. SHOW_ELEMENT, element node.
  •  nodefilter. SHOW_ATTRIBUTE, attribute node. Because of the STRUCTURE of the DOM, you don’t really need it.
  •  nodefilter. SHOW_TEXT, text node.
  •  nodefilter. SHOW_CDATA_SECTION, CData block node. Not used in HTML pages.
  •  nodefilter. SHOW_ENTITY_REFERENCE, the entity refers to the node. Not used in HTML pages.
  •  nodefilter. SHOW_ENTITY: entity node. Not used in HTML pages.
  •  nodefilter. SHOW_PROCESSING_INSTRUCTION, processing instruction node. Not used in HTML pages.
  •  nodefilter. SHOW_COMMENT Comment node.
  •  nodefilter. SHOW_DOCUMENT, document node.
  •  nodefilter. SHOW_DOCUMENT_TYPE, document type node.
  •  nodefilter. SHOW_DOCUMENT_FRAGMENT, document fragment node. Not used in HTML pages.
  •  nodefilter. SHOW_NOTATION, specifies the node. Not used in HTML pages.

All of these values can be combined except nodefilter.show_all. For example, multiple options can be combined using bitwise or actions like the following:

let whatToShow = NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT;
Copy the code

The filter argument to the createNodeIterator() method can be used to specify a custom NodeFilter object, or a function that acts as a NodeFilter. The NodeFilter object has only one method, acceptNode(), which returns nodefilter.filter_accept if the given node should access it, or nodefilter.filter_skip otherwise. Because NodeFilter is an abstract type, it is not possible to create an instance of it. Just create an acceptNode() object and pass it to createNodeIterator().

The following code defines only receive

Element node filter object:

let filter = { 
 acceptNode(node) { 
 	return node.tagName.toLowerCase() == "p"? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP; }};let iterator = document.createNodeIterator(root, NodeFilter.SHOW_ELEMENT, 
 filter, false);
Copy the code

The filter argument can also be a function, of the same form as acceptNode(), as shown in the following example:

let filter = function(node) { 
 return node.tagName.toLowerCase() == "p" ? 
 NodeFilter.FILTER_ACCEPT : 
 NodeFilter.FILTER_SKIP; 
}; 
let iterator = document.createNodeIterator(root, NodeFilter.SHOW_ELEMENT, 
 filter, false);
Copy the code

Typically, JavaScript uses this form because it’s simpler and more like normal JavaScript code. If you do not need to specify a filter, you can pass null to this parameter.

To create a simple NodeIterator that iterates through all nodes, use the following code:

let iterator = document.createNodeIterator(document, NodeFilter.SHOW_ALL, 
 null.false);
Copy the code

The two main methods for NodeIterator are nextNode() and previousNode(). The nextNode() method takes a depth-first step forward in the DOM subtree, while previousNode() takes a step back in the traversal. When you create a NodeIterator, there is an internal pointer to the root node, so the first call to nextNode() returns the root node. NextNode () returns NULL when the traversal reaches the last node in the DOM tree. The previousNode() method is similar. When the traversal reaches the last node in the DOM tree, a call to previousNode() returns the traversal root node, and a second call returns NULL.

Take the following HTML snippet for example:

<div id="div1"> 
 <p><b>Hello</b> world!</p> 
 <ul> 
 <li>List item 1</li> 
 <li>List item 2</li> 
 <li>List item 3</li> 
 </ul> 
</div>
Copy the code

The hypothesis is traversed

For all elements within the element, you can use the following code:

let div = document.getElementById("div1"); 
let iterator = document.createNodeIterator(div, NodeFilter.SHOW_ELEMENT, 
 null.false); 
let node = iterator.nextNode(); 
while(node ! = =null) { 
 console.log(node.tagName); // Prints the label name
 node = iterator.nextNode(); 
}
Copy the code

The first call to nextNode() in this example returns

Elements. Because nextNode() returns NULL when traversal reaches the end of the DOM subtree, a while loop is used here to check whether the return value of each call to nextNode() is null. The following tag names are output after the above code is executed:

DIV 
P 
B 
UL 
LI 
LI 
LI
Copy the code

If YOU just want to traverse

  • Element, you can pass in a filter, such as:

    let div = document.getElementById("div1"); 
    let filter = function(node) { 
     return node.tagName.toLowerCase() == "li" ? 
     NodeFilter.FILTER_ACCEPT : 
     NodeFilter.FILTER_SKIP; 
    }; 
    let iterator = document.createNodeIterator(div, NodeFilter.SHOW_ELEMENT, 
     filter, false);
    let node = iterator.nextNode(); 
    while(node ! = =null) { 
     console.log(node.tagName); // Prints the label name
     node = iterator.nextNode(); 
    }
    Copy the code

    In this case, traversal only outputs

  • The label of the element.

    The nextNode() and previousNode() methods jointly maintain the NodeIterator’s internal pointer to the DOM structure, so changes to the DOM structure are also reflected in the traversal.

    TreeWalker

    TreeWalker is an advanced version of NodeIterator. In addition to containing the same nextNode() and previousNode() methods, TreeWalker also adds the following methods to traverse the DOM structure in different directions.

    •  parentNode(), traverses to the parentNode of the current node.
    •  firstChild(), traversing to the firstChild of the current node.
    •  lastChild(), traversing to the lastChild of the current node.
    •  nextSibling(), traversing to the nextSibling of the current node.
    •  previousSibling(), traversing to the last sibling of the current node.

    TreeWalker object to call the document. CreateTreeWalker () method to create, this method takes with the document. The createNodeIterator () the same parameters: The root node as the starting point for traversal, the type of node to view, the node filter, and a Boolean value indicating whether to extend the entity reference. Because the two are similar, TreeWalker can often replace NodeIterator, for example:

    let div = document.getElementById("div1"); 
    let filter = function(node) { 
     return node.tagName.toLowerCase() == "li" ? 
     NodeFilter.FILTER_ACCEPT : 
     NodeFilter.FILTER_SKIP; 
    };
    let walker = document.createTreeWalker(div, NodeFilter.SHOW_ELEMENT, 
     filter, false); 
    let node = iterator.nextNode(); 
    while(node ! = =null) { 
     console.log(node.tagName); // Prints the label name
     node = iterator.nextNode(); 
    }
    Copy the code

    The NodeFilter can return nodefilter.filter_accept and nodefilter.filter_skip as well as nodefilter.filter_reject. When using NodeIterator, nodefilter_skip is the same as nodefilter.filter_reject. But with TreeWalker, nodefilter.filter_skip means to skip the node and access the next node in the subtree, while nodefilter.filter_reject means to skip the node and the entire subtree of the node.

    For example, if the filter function in the previous example were changed to return nodefilter_reject (instead of nodefilter.filter_skip), it would result in traversal returning immediately without accessing any nodes. This is because the first element returned is

    , where the label name is not “li”, so the filter function returns nodefilter.filter_ REJECT, which means to skip the entire subtree. because

    Is itself the root of the traversal, so the traversal will end there.

    Of course, the TreeWalker’s real power is its ability to move around the DOM structure. The roaming capabilities of the TreeWalker can also be accessed in the DOM tree without filters

  • Elements, such as:

    let div = document.getElementById("div1"); 
    let walker = document.createTreeWalker(div, NodeFilter.SHOW_ELEMENT, null.false); 
    walker.firstChild(); / / to < p >
    walker.nextSibling(); / / to the < ul >
    let node = walker.firstChild(); // go to first 
  • while(node ! = =null) { console.log(node.tagName); node = walker.nextSibling(); } Copy the code

    Because we know

  • The location of the element in the document structure, so it can be located directly past. First use firstChild() to go forward

    Element, which is then approached via nextSibling()

      Element, and then use firstChild() to reach the first

    • Elements. Notice that the TreeWalker at this point only returns elements (this is because of the second argument passed to createTreeWalker()). Finally, you can access each using nextSibling()
    • Element until there are no more elements, at which point the method returns NULL.

      The TreeWalker type also has an attribute named currentNode, which represents the last node returned during the traversal (regardless of the traversal method used). You can modify this property to influence the starting point of the subsequent traversal, as shown in the following example:

      let node = walker.nextNode(); 
      console.log(node === walker.currentNode); // true 
      walker.currentNode = document.body; // Modify the starting point
      Copy the code

      The TreeWalker type provides more flexibility to traverse the DOM than the NodeIterator.

      The scope of

      DOM range

      DOM2 defines a createRange() method on the Document type, exposed on the Document object. You can use this method to create a DOM scope object as follows:

      let range = document.createRange();
      Copy the code

      Like a node, this newly created scope object is associated with the document that created it and cannot be used in other documents. You can then use this scope to select a specific part of the document behind the scenes. Once you have created a scope and specified its location, you can perform some operations on the contents of the scope to achieve finer control over the underlying DOM tree.

      Each scope is an instance of type Range with corresponding properties and methods. The following properties provide information about the location of the scope in the document.

      •  startContainer, the node where the range starts (the parent of the first child in the selection).
      •  startOffset, the offset of the start of the range in startContainer. If startContainer is a text node, comment node, or CData block node, startOffset refers to the number of characters skipped before the start of the range; Otherwise, represents the index of the first node in the range.
      •  endContainer, the node where the range ends (the parent of the last child in the selection).
      •  endOffset, offset of the starting point of a range in startContainer (the same meaning as offset in startOffset).
      •  commonAncestorContainer, startContainer and endContainer are the deepest nodes in the documentation.

      These properties get values when the scope is placed at a specific location in the document.

      Simple choices

      The easiest way to select a part of a document by scope is to use the selectNode() or selectNodeContents() methods. Both methods take a node as an argument and add information about that node to the scope in which it is invoked. The selectNode() method selects the entire node, including its descendants, while selectNodeContents() selects only the descendants of the node.

      Suppose you have the following HTML:

      <! DOCTYPEhtml> 
      <html> 
       <body> 
       <p id="p1"><b>Hello</b> world!</p> 
       </body> 
      </html>
      Copy the code

      The following JavaScript code can access and create the corresponding scope:

      let range1 = document.createRange(), 
       range2 = document.createRange(), 
       p1 = document.getElementById("p1"); 
      range1.selectNode(p1); 
      range2.selectNodeContents(p1);
      Copy the code

      The two scopes in the example contain different parts of the document. Range1 contains

      Element and all of its descendants, while range2 contains the element, the text node “Hello”, and the text node “world!”

      When selectNode() is called, startContainer, endContainer, and commonAncestorContainer are all equal to the parent of the passed node. In this case, these attributes are equal to document.body. The startOffset property is equal to the index of the incoming node in its parent childNodes collection (in this case, startOffset is equal to 1, because the DOM compliance implementation treats Spaces as text nodes), EndOffset is equal to startOffset plus 1 (because only one node is selected).

      When selectNodeContents() is called, the startContainer, endContainer, and commonAncestor Container properties are the nodes passed in, in this case

      Elements. The startOffset property is always 0 because the range starts at the first child of the incoming node, and endOffset is equal to the number of children of the incoming node (node.child Nodes.length), which in this case is equal to 2.

      After selecting a node or descendant of a node as above, you can also call the corresponding method on the scope for finer control over the selection in the scope.

      • setStartBefore(refNode), set the starting point of the range torefNodeBefore, so thatrefNodeBecomes the first child of the selection. The startContainer property is set to refNode.parentNode and the startOffset property is set torefNodeIndex in the collection of its parent childNodes.
      • setStartAfter(refNode), set the starting point of the range torefNodeAnd then we’re going torefNodeThe next sibling becomes the first child of the selection by excluding it from the selection. The startContainer property is set to refNode.parentNode, and the startOffset property is set torefNodeIncrements the index in the collection of its parent childNodes by 1.
      • setEndBefore(refNode), set the end of the range torefNodeBefore, and thus willrefNodeExcluding the selection, making its previous sibling the last child of the selection. The endContainer property is set to refNode. parentNode, and the endOffset property is set torefNodeIndex in the collection of its parent childNodes.
      • setEndAfter(refNode), set the end of the range torefNodeAnd then, let’srefNodeBecomes the last child node of the selection. The endContainer property is set to refNode.parentNode, and the endOffset property is set torefNodeIncrements the index in the collection of its parent childNodes by 1.

      When these methods are called, all properties are automatically reassigned. However, it is also possible to modify the values of these attributes directly for complex selection.

      Complex choice

      To create complex scopes, use the setStart() and setEnd() methods. Both methods take two parameters: the reference node and the offset. For setStart(), the reference node becomes startContainer and the offset is assigned to startOffset. For setEnd(), the reference node becomes endContainer and the offset is assigned to the endOffset.

      With these two methods, you can simulate the behavior of selectNodes () and selectNodeContents(). Such as:

      let range1 = document.createRange(), 
       range2 = document.createRange(), 
       p1 = document.getElementById("p1"), 
       p1Index = -1, 
       i, 
       len; 
      for (i = 0, len = p1.parentNode.childNodes.length; i < len; i++) { 
       if (p1.parentNode.childNodes[i] === p1) { 
       p1Index = i; 
       break; 
       } 
      }
      range1.setStart(p1.parentNode, p1Index); 
      range1.setEnd(p1.parentNode, p1Index + 1); 
      range2.setStart(p1, 0); 
      range2.setEnd(p1, p1.childNodes.length);
      Copy the code

      Note that to select a node (using range1), you must first determine the index of the given node (P1) in its parent childNodes collection. To select the contents of the node (using range2), you don’t need to do this calculation because you can pass default values to setStart() and setEnd() directly. While you can simulate selectNodes () and selectNodeContents(), the real power of setStart() and setEnd() is to select parts of a node.

      Suppose we want to choose from the previous example by scope from “llo” in “Hello” to “world!” The “O” part of the. Quite simply, the first step is to get references to all relevant nodes, as shown in the following code:

      let p1 = document.getElementById("p1"), 
       helloNode = p1.firstChild.firstChild, 
       worldNode = p1.lastChild
      Copy the code

      The text “Hello” is actually

      Grandchild node, because it is a child node. To do this, use p1.firstChild, and use p1.firstChild.firstChild to get the text node “Hello”. The text node “World!” is

      The second (and last) child of the object, so it can be obtained using p1.lastChild. Then, create the scope and specify its boundaries as follows:

      let range = document.createRange(); 
      range.setStart(helloNode, 2); 
      range.setEnd(worldNode, 3);
      Copy the code

      Because the selection starts after the letter “e” in “Hello”, setStart() is passed helloNode and offset 2 (after “e”, “H” is 0). To set the end of the selection, we pass setEnd() worldNode and offset 3, the location of the first character that is not part of the selection, which is position 3 of “r” (position 0 is a space).

      Since helloNode and worldNode are text nodes, they become startContainer and endContainer for the scope, so startOffset and endOffset actually represent the location of the local character in each node. Not the location of the child node (as when the element node is passed in). And commonAncestorContainer is

      Element, which is the first ancestor node containing these two nodes.

      Of course, selecting only one part of the document is not particularly useful unless you can perform operations on the selected part.

      Operating range

      After the scope is created, the browser internally creates a document fragment node to contain the nodes in the scope selection. Is the content of the operation scope, the content in the selection must be well-formed. In the previous example, the scope cannot be represented in the DOM because it starts and ends inside the text node and is not an intact DOM structure. However, scopes can identify missing start and end tags, allowing you to reconstruct a valid DOM structure for subsequent operations.

      Using the scope in the previous example, the scope finds that a starting tag is missing from the selection, and dynamically fills in this tag in the background, along with the closing tag enclosing “He”. The result is to change the DOM to something like this:

      <p><b>He</b><b>llo</b> world!</p>
      Copy the code

      The first method is the easiest to understand and use: deleteContents(). As the name suggests, this method removes scoped nodes from the document. Here’s an example:

      let p1 = document.getElementById("p1"), 
       helloNode = p1.firstChild.firstChild, 
       worldNode = p1.lastChild, 
       range = document.createRange(); 
      range.setStart(helloNode, 2); 
      range.setEnd(worldNode, 3); 
      range.deleteContents();
      Copy the code

      After executing the above code, the HTML in the page would look like this:

      <p><b>He</b>rld!</p>
      Copy the code

      Because the scope selection process described earlier keeps the structure intact by modifying the underlying DOM structure, the DOM structure remains intact even after the scope is deleted.

      Another method, extractContents(), is similar to deleteContents() and also removes scoped selections from the document. The difference, however, is that the extractContents() method returns the document fragment corresponding to the scope. This allows you to insert the scoped content elsewhere in the document. Here’s an example:

      let p1 = document.getElementById("p1"), 
       helloNode = p1.firstChild.firstChild, 
       worldNode = p1.lastChild, 
       range = document.createRange();
      range.setStart(helloNode, 2); 
      range.setEnd(worldNode, 3); 
      let fragment = range.extractContents(); 
      p1.parentNode.appendChild(fragment);
      Copy the code

      This example extracts the document fragment of the scope and then adds it to the end of the document element. (Remember that when you pass a document fragment to appendChild(), only the subtree of the fragment is added, not the fragment itself.) The result should be the following HTML:

      <p><b>He</b>rld!</p> 
      <b>llo</b> wo
      Copy the code

      If you don’t want to remove the scope from the document, you can also use cloneContents() to create a copy and insert that copy elsewhere in the document. Such as:

      let p1 = document.getElementById("p1"), 
       helloNode = p1.firstChild.firstChild, 
       worldNode = p1.lastChild, 
       range = document.createRange(); 
      range.setStart(helloNode, 2); 
      range.setEnd(worldNode, 3); 
      let fragment = range.cloneContents(); 
      p1.parentNode.appendChild(fragment);
      Copy the code

      This method is similar to extractContents() in that they both return document fragments. The main difference is that the document fragment returned by cloneContents() contains copies of the nodes in the scope, rather than the actual nodes. After doing the above, the HTML page looks like this:

      <p><b>Hello</b> world!</p> 
      <b>llo</b> wo
      Copy the code

      The key thing to know at this point is that splitting a node to keep the structure intact only happens when the method above is called. Until the DOM is modified, the raw HTML remains the same.

      Scope of insert

      The previous section covered removing and copying a range. This section takes a look at inserting content into a range. Use the insertNode() method to insert a node at the beginning of a range selection. For example, suppose we want to insert the following HTML into the HTML from the previous example:

      <span style="color: red">Inserted text</span>
      Copy the code

      You can use the following code:

      let p1 = document.getElementById("p1"), 
       helloNode = p1.firstChild.firstChild, 
       worldNode = p1.lastChild, 
       range = document.createRange(); 
      range.setStart(helloNode, 2); 
      range.setEnd(worldNode, 3);
      
      let span = document.createElement("span"); 
      span.style.color = "red"; 
      span.appendChild(document.createTextNode("Inserted text")); 
      range.insertNode(span);
      Copy the code

      Running the above code yields the following HTML code:

      <p id="p1"><b>He<span style="color: red">Inserted text</span>llo</b> world</p>
      Copy the code

      Notice that it is inserted just before the “llo” in “Hello”, just before the scope selection. Also, note that the original HTML does not add or remove elements, because the method mentioned earlier is not used here. You can use this technique to insert useful information, such as a small icon next to an external link.

      In addition to inserting content into a scope, you can use the surroundContents() method to insert content that contains a scope. This method takes one parameter, the node that contains the content of the scope. When this method is called, the following is done in the background:

      (1) Extracting the contents of the scope;

      (2) Insert the given node at the position before the scope in the original document;

      (3) Add the content of the document fragment corresponding to the scope to the given node.

      This feature is good for highlighting certain keywords in a web page, such as:

      let p1 = document.getElementById("p1"), 
       helloNode = p1.firstChild.firstChild, 
       worldNode = p1.lastChild, 
       range = document.createRange();
      
      range.selectNode(helloNode); 
      let span = document.createElement("span"); 
      span.style.backgroundColor = "yellow"; 
      range.surroundContents(span);
      Copy the code

      Executing the code above highlights the text of the range selection on a yellow background. The resulting HTML looks like this:

      <p><b><span style="background-color:yellow">Hello</span></b> world!</p>
      Copy the code

      To insert elements, the scope must contain the full DOM structure. This operation will fail with an error if the range contains partially selected non-text points. Also, if the given node is of the Document, DocumentType, or DocumentFragment type, it will result in a throw error.

      The scope of folding

      If the scope does not select any part of the document, it is called collapsed. Collapsing ranges are a bit like text boxes: If there is text in a text box, you can highlight the entire text with your mouse. If you click again, the selection is removed and the cursor lands between two characters. When you fold a scope, the position is set to where the scope meets the document, either at the beginning or the end of the scope selection.

      Collapse ranges can be collapsed using the collapse() method, which accepts a single parameter: a Boolean value indicating which end of the range to collapse to. True folds to the beginning and false folds to the end. To determine whether an area has collapsed, the collapsed property can be detected:

      range.collapse(true); // Fold to the starting point
      console.log(range.collapsed); / / output true
      Copy the code

      Testing whether the scope is collapsed can help determine whether two nodes in the scope are adjacent. For example, there is the following HTML code:

      <p id="p1">Paragraph 1</p><p 
      id="p2">Paragraph 2</p>
      Copy the code

      If the structure of the tag is not known in advance (such as automatically generated tags), you can create a range like this:

      let p1 = document.getElementById("p1"), 
       p2 = document.getElementById("p2"), 
       range = document.createRange(); 
      range.setStartAfter(p1); 
      range.setStartBefore(p2); 
      console.log(range.collapsed); // true
      Copy the code

      In this case, the range created is collapsed because there is nothing after P1 and before P2.

      Scope of comparison

      If you have more than one scope, you can use the compareBoundaryPoints() method to determine whether there is a common boundary (starting or ending point) between the scopes. This method takes two parameters: the range to compare and a constant value that indicates how the comparison is performed. The constant parameters include:

      •  range.start_to_start (0), comparing the start of the two ranges;
      •  range.start_to_end (1), comparing the start of the first Range with the end of the second Range;
      •  range.end_to_end (2), comparing the ends of two ranges;
      •  range.end_to_start (3), comparing the end of the first Range with the beginning of the second Range.

      The compareBoundaryPoints() method returns -1 if the boundary point of the first range is in front of the boundary point of the second range, 0 if the boundary points of the two ranges are equal, and 1 if the boundary point of the first range is behind the boundary point of the second range. Consider the following example:

      let range1 = document.createRange(); 
      let range2 = document.createRange(); 
      let p1 = document.getElementById("p1"); 
      range1.selectNodeContents(p1); 
      range2.selectNodeContents(p1); 
      range2.setEndBefore(p1.lastChild); 
      console.log(range1.compareBoundaryPoints(Range.START_TO_START, range2)); / / 0
      console.log(range1.compareBoundaryPoints(Range.END_TO_END, range2));  / / 1
      Copy the code

      In this code, the starting points of the two ranges are equal because they are both the default values returned by selectNodeContents().

      Therefore, the method that compares the starting points of the two returns 0. However, because the end of range2 was modified using setEndBefore(), so

      The result is that the end of range1 is after the end of range2 (see Figure 16-11), resulting in the method returning 1.

      Copy the scope

      The cloneRange() method that calls the scope copies the scope. This method creates a copy of the scope on which it was called:

      let newRange = range.cloneRange();
      Copy the code

      The new scope contains the same attributes as the original scope, and modifying its boundary points does not affect the original scope.

      Clean up the

      When you’re done with the scope, it’s a good idea to call the detach() method to strip the scope from the document that created it. After detach() is called, the scope can be safely dereferenced so that the garbage collector can free up its memory. Here’s an example:

      range.detach(); // Strip the scope from the document
      range = null; // Dereference
      Copy the code

      These two steps are the most logical way to end the scope of use. The stripped range is no longer usable.