“The front frame smells so good, I can’t tear DOM with my bare hands!”
Most front-end ERS have this problem, but in keeping with the basics, ripping the DOM by hand should be a must-have skill for a front-end siege lion, which is why this article was born — the DOM isn’t that difficult, and if you can make the most of it, you’ll soon love it.
When I first entered the front-end pit three years ago, I discovered a treasure called jQuery that had a magical $function that allowed me to quickly select a DOM element or set of DOM elements and provide chain calls to reduce code redundancy. Although the term jQuery may sound corny now, “9102 years and you’re talking to me about Nokia?” . Dust to dust, but it really smells good. ** Although the decline of jQuery has been exacerbated by Vue and React in recent years, jQuery is still used on over 66 million websites worldwide, accounting for 74% of all websites in the world.
JQuery has also left a legacy, with the W3C implementing querySelector and querySelectorAll, modeled after its $function. Ironically, it is these two native methods that have greatly accelerated the demise of jQuery because they have replaced one of the former’s most common features, the quick selection of DOM elements.
Although these two new methods are a bit long to write (no problem, encapsulate them), they are really useful.
I’m going to start with the querySelector, which is pretty common now, and introduce a nice wave of DOM apis. Come on, rush!
Get the DOM element
Getting a single element
Select a single DOM element by passing any valid CSS selector to Document. querySelector:
document.querySelector('.element')
document.querySelector('#element')
document.querySelector('div')
document.querySelector('[name="username"]')
document.querySelector('div + p > span')
Copy the code
Returns null if there is no specified element on the page
Get the collection of elements
Using document. QuerySelectorAll can obtain a collection of elements that its participation and document. QuerySelector a MAO. It returns a ** static NodeList **, or an empty NodeList if no element is found.
NodeList is a traversable object (aka pseudo-array). Although it is similar to an array, it is not an array. Although it can be traversed by forEach, it does not have some of the methods of an array, such as Map, Reduce, and find.
So, how does ** convert a pseudo-array into an array? **ES6 offers two convenient options for developers:
const arr = [...document.querySelectorAll('div')]
// or
const alsoArr = Array.from(document.querySelectorAll('div'))
Copy the code
In ancient times, getElementsByTagName and getElementsByClassName were used to retrieve collections of elements, but unlike querySelectorAll, they retrieve a dynamic HTMLCollection. This means that its results will always change as the DOM changes.
Local search of elements
When you need to look up elements, you don’t have to do it based on document every time. Developers can perform a local search for DOM elements on any HTMLElement:
const container = document.querySelector('#container')
container.querySelector('#target')
Copy the code
Too much typing hello!
As it turns out, every good developer is lazy. To minimize the wear and tear on my baby keyboard, HERE’s what I do:
const$=document.querySelector.bind(document)
Copy the code
Protect the mechanical keyboard, start from me.
Boy, climb the DOM tree
The topic of this article is finding DOM elements, which is a top-down process: a parent element initiates a query to its contained child elements.
But there is no API to help developers query parent elements from child elements.
Closest to the problem, MDN offered me a treasure trove: Closest.
Starting with the
Element
itself, theclosest()
method traverses parents (heading toward the document root) of theElement
until it finds a node that matches the provided selectorString. Will return itself or the matching ancestor. If no such element exists, it returnsnull
.
That is, closest looks upwards from a specific HTMLElement, finds the first parent element (or the element itself) that matches a specified CSS expression, and returns NULL if the document root is found and the target is not yet found.
Add a DOM element
If native JavaScript is used to add one or more elements to the DOM, the average developer’s gut will resist. Why? Suppose you add an A tag to the page:
<a href="/home" class="active">Home page</a>
Copy the code
Normally, you would write code like this:
const link = document.createElement('a')
link.setAttribute('href'.'/home')
link.className = 'active'
link.textContent = 'home'
// finally
document.body.appendChild(link)
Copy the code
Really trouble.
JQuery can be simplified as:
$('body').append(')
Copy the code
But, folks, it’s now possible to do this with native JavaScript:
document.body.insertAdjacentHTML(
'beforeend'.'
)
Copy the code
This method allows you to insert any valid HTML string into a DOM element at four positions specified by the first argument to the method:
- ‘beforeBEGIN ‘: Precedes the element
- ‘AfterBEGIN ‘: Inside the element, before the first existing child element
- ‘beforeend’: Inside the element, after the last existing child element
- ‘afterend’: after the element
<! -- beforebegin -->
<div>
<! -- afterbegin -->
<span></span>
<! -- beforeend -->
</div>
<! -- afterend -->
Copy the code
Comfortable.
Even better, it also has two nice brothers that allow developers to quickly insert HTML elements and strings:
// Insert HTML elements
document.body.insertAdjacentElement(
'beforeend'.document.createElement('a'))// Insert text
document.body.insertAdjacentText('afterbegin'.'cool! ')
Copy the code
Moving DOM elements
The insertAdjacentElement method mentioned above can also be used to move existing elements, in other words: when an element is passed into the method that already exists in the document, that element will only be moved (rather than copied and moved).
If you have the following HTML:
<div class="first">
<h1>Title</h1>
</div>
<div class="second">
<h2>Subtitle</h2>
</div>
Copy the code
Then do something to put
after
:
const h1 = document.querySelector('h1')
const h2 = document.querySelector('h2')
h1.insertAdjacentElement('afterend', h2)
Copy the code
So we get something like this:
<div class="first">
<h1>Title</h1>
<h2>Subtitle</h2>
</div>
<div class="second">
</div>
Copy the code
Replacing DOM elements
replaceChild? This was done a few years ago. Whenever a developer needed to replace two DOM elements, they needed to retrieve their immediate parent element in addition to the required two elements:
parentNode.replaceChild(newNode, oldNode)
Copy the code
Now, developers can use replaceWith to replace two elements:
oldElement.replaceWith(newElement)
Copy the code
In terms of usage, a little more refreshing than the former.
Note that:
- If the newElement passed in already exists in the document, the result of the method execution is that the newElement is moved and the oldElement is replaced
- If the newElement passed in is a string, it replaces the original element as a TextNode
Remove the DOM element
As with the old method of replacing an element, the old method of removing an element requires obtaining the immediate parent of the target element:
const target = document.querySelector('#target')
target.parentNode.removeChild(target)
Copy the code
Now we just need to execute the remove method once on the target element:
const target = document.querySelector('#target')
target.remove()
Copy the code
Create DOM elements with HTML strings
As you may have noticed, the insertAdjacent method allows developers to insert HTML directly into the document. What if we just want to generate a DOM element for future use?
The DOMParser object’s parseFromString method satisfies this requirement. This method converts an HTML or XML string into a DOM document. That is, when we need to get the expected DOM element, we need to get the DOM element from the DOM document returned by the method:
const createSingleElement = (domString) = > {
const parser = new DOMParser()
return parser.parseFromString(domString, 'text/html').body.firstChild
}
// usage
const element = createSingleElement('<a href="./home">Home</a>')
Copy the code
Be a dab hand at checking the DOM
The standard DOM API provides many convenient ways for developers to examine the DOM. For example, the matches method checks whether an element matches a certain selector:
//
Hello DOM!
const div = document.querySelector('div')
div.matches('div') // true
div.matches('.say-hi') // true
div.matches('#hi') // false
Copy the code
The contains method checks if an element contains another element (or if an element is a child of another element) :
// <div><h1>Title</h1></div>
// <h2>Subtitle</h2>
const$=document.querySelector.bind(document)
const div = $('div')
const h1 = $('h1')
const h2 = $('h2')
div.contains(h1) // true
div.contains(h2) // false
Copy the code
One trick: compareDocumentPosition
CompareDocumentPosition is a powerful API that can quickly determine the position of two DOM elements such as precede, follow, and contain. It returns an integer representing the relationship between two elements.
Still in the example above / / container.com pareDocumentPosition (h1) / / 20 h1.com pareDocumentPosition (container) / / 10 h1.compareDocumentPosition(h2) // 4 h2.compareDocumentPosition(h1) // 2Copy the code
Standard statement:
element.compareDocumentPosition(otherElement)
Copy the code
The return value is defined as follows:
- 1: Two elements are not in the same document
- 2: otherElement precedes element
- 4: otherElement comes after element
- 8: otherElement contains element
- OtherElement is contained by an element
So why is the result of the first row in the above example 20 and 10 in the second row?
H1 is both contained by container (16) and after container (10), so 16+4=20 is the result of the statement.
DOM Observer: MutationObserver
When dealing with user interaction, there are often many changes to the DOM elements of the current page, and some scenarios require developers to listen for these changes and perform actions when triggered. MutationObserver is a browser-provided interface designed to listen for DOM changes. It is powerful enough to observe almost all changes to an element, including text changes, addition and removal of child nodes, and changes to any element attributes.
As always, if you want to construct any object, new its constructor:
const observer = new MutationObserver(callback)
Copy the code
The constructor is passed in a callback function that is executed when the DOM element being listened on changes, taking MutationRecords, the list containing all of the changes, and the Observer itself. Where each entry of MutationRecords is a change record, it is a common object containing the following common properties:
- Type: Change type, Attributes/characterData/childList
- Target: the DOM element that was changed
- AddedNodes: Adds NodeList consisting of child elements
- RemovedNodes: NodeList with removed child elements
- AttributeName: The name of the attribute whose value changed, or null if not
- PreviousSibling: The sibling node before the child element to be added or removed
- NextSibling: The sibling node after the child element that is added or removed
Based on the current information, we can write a callback function:
const callback = (mutationRecords, observer) = > {
mutationRecords.forEach({
type,
target,
attributeName,
oldValue,
addedNodes,
removedNodes,
} => {
switch(type) {
case 'attributes':
console.log(`attribute ${attributeName} changed`)
console.log(`previous value: ${oldValue}`)
console.log(`current value: ${target.getAttribite(attributeName)}`)
break
case 'childList':
console.log('child nodes changed')
console.log('added: ${addedNodes}')
console.log('removed: ${removedNodes}')
break
// ...}})}Copy the code
At this point, we have a DOM observer, and a fully available DOM callback, except for one DOM element that needs to be observed:
const target = document.querySelector('#target')
observer.observe(target, {
attributes: true.attributeFilter: ['class'].attributesOldValue: true.childList: true,})Copy the code
In the above code, we observe the DOM element id target (the first parameter is the object to observe) by calling the observe object’s observe method. For the second element, we pass in a configuration object: Enable observation of attributes/Only observation of class attributes/passing old values of attributes when attributes change/Enable observation of lists of child elements.
The configuration object supports the following fields:
- Attributes: Boolean, whether to listen for changes to element attributes
- AttributeFilter: String[], an array of specific attribute names to listen on
- AttributeOldValue: Boolean, whether the last value of the listening element’s attribute is logged and passed when the attribute changes
- CharacterData: Boolean, whether to listen for changes in characterData contained by nodes in the target element or child element tree
- CharacterDataOldValue: Boolean, whether to record and pass the previous value of character data when it changes
- ChildList: Boolean, whether to listen to the target element to add or remove child elements
- Subtree: Boolean, whether to extend the monitoring scope to all elements of the entire subtree under the target element
When the change of the target element is no longer monitored, the Observer’s Disconnect method can be called. If necessary, the Observer’s takeRecords method can be first called to remove all pending notifications from the Observer’s notification queue. And returns them to an array of MutationRecord objects:
const mutationRecords = observer.takeRecords()
callback(mutationRecords)
observer.disconnect()
Copy the code
Don’t be afraid of DOM
Although most DOM apis have long (and cumbersome to write) names, they are very powerful and generic. These apis are often designed to provide developers with the underlying building blocks on which to build more general and concise abstract logic, so from this perspective, they must provide a full name to be clear and unambiguous.
As long as these apis live up to the potential they’re supposed to live up to, what’s a few more keystrokes?
DOM is essential knowledge for every JavsScript developer because we use it almost every day. Don’t be afraid to use your primitive DOM skills and become a DOM senior engineer as soon as possible.