DOM senior engineer incomplete Guide

Time:2020-1-16

Use the DOM like a pro
Translator: kyrieliu

“The front frame is so fragrant that I dare not tear DOM by myself!”

Although the vast majority of front-end ers have such problems, but based on the principle of big foundation, tearing DOM by hand should be a necessary skill for front-end siege lions, which is exactly the original intention of this article – DOM is not so difficult to handle, if you can make full use of it, then you are not far from falling in love with it.

When I first entered the front-end pit three years ago, I found a baby called jQuery. She has a magic $function that allows me to quickly select a DOM element or group of DOM elements and provide chain calls to reduce code redundancy. Although jQuery is now mentioned, you will feel old-fashioned, “you told me Nokia in 9102?”.The earth is the earth, but it is also the real fragrance.Despite the ups and downs of Vue and react in recent years, jQuery is still used by more than 66 million websites around the world, accounting for 74% of all websites in the world.

JQuery also left a far-reaching “legacy” for the industry. W3C implemented queryselector and queryselectorall in imitation of its $function. Ironically, the emergence of these two native methods greatly accelerated the decline of jQuery, because they replaced the former’s most commonly used function, which is to quickly select DOM elements.

Although these two new methods are a little long to write (no problem, they are really easy to use).

Come on, rush!

Get DOM element

Get single element

Pass in any valid CSS selector to the document.queryselector to select a single DOM element:

document.querySelector('.element')
document.querySelector('#element')
document.querySelector('div')
document.querySelector('[name="username"]')
document.querySelector('div + p > span')

Returns NULL if there is no element specified on the page

Get element collection

Using document.queryselectorall, you can get a collection of elements whose pass through parameters are the same as document.queryselector. It will return aStatic NodeList, if no element is found, an empty NodeList is returned.

NodeList is a traversable object (aka: pseudo array). Although it is similar to array, it is not array. Although it can be traversed by foreach, it does not have some methods of array, such as map, reduce, find.

So here comes the question…,How to convert a pseudo array into an array?ES6 provides two convenient choices for developers:

const arr = [...document.querySelectorAll('div')]
// or
const alsoArr = Array.from(document.querySelectorAll('div'))

In ancient times, developers often used getElementsByTagName and getelementsbyclassname to get the element collection, but unlike queryselectorall, they got aDynamic htmlcollection, which means that its results will always change as the DOM changes.

Local search for elements

When you need to find elements, you don’t have to search based on document every time. Developers can search for DOM elements locally on any HTML element:

const container = document.querySelector('#container')
container.querySelector('#target')

Too much typing Hello!

It turns out that every good developer is lazy. In order to reduce the loss of the baby keyboard, I usually do this:

const $ = document.querySelector.bind(document)

Protect the mechanical keyboard from me.

Boy, climb up this DOM tree

The theme of the above isFind DOM elements, which is a top-down process: starting a query from the parent element to the child element it contains.

However, there is no API to help developers initiate queries from child elements to parent elements.

When confused, MDN provides me with a treasure trove method: closest.

Starting with the Element itself, the closest() method traverses parents (heading toward the document root) of the Element until it finds a node that matches the provided selectorString. Will return itself or the matching ancestor. If no such element exists, it returns null.

In other words, the closest method can initiate a query from a specific HTML element up to find the first parent element (or the element itself) that matches the specified CSS expression. If the root node of the document is found and the target is not found, it will return null.

Add DOM element

If you use native JavaScript to add one or more elements to the DOM, the heart of the general developer is resistant. Why? Suppose you add an a tag to the page:

< a href = "/ home" class = "active" > Home Page</a>

Normally, you need to write the following code:

const link = document.createElement('a')
link.setAttribute('href', '/home')
link.className = 'active'
Link. Textcontent = 'first page'

// finally
document.body.appendChild(link)

It’s a real hassle.

JQuery can be simplified as follows:

$('body '). Append (' < a href = "/ home" class = "active" > Home Page < / a > ')

But, audience, now native JavaScript can also do this:

document.body.insertAdjacentHTML(
    'beforeend',
  '< a href = "/ home" class = "active" > Home Page < / a >'
)

This method allows you to insert any valid HTML string into four positions of a DOM element, which are specified by the first parameter of the method, namely:

  • ‘before begin’: before element
  • ‘afterBegin’: within an element, before the first existing child element
  • ‘before end’: within an element, after the last existing child element
  • ‘afterend’: after element
<!-- beforebegin -->
<div>
    <!-- afterbegin -->
  <span></span>
     <!-- beforeend -->
</div>
<!-- afterend -->

It’s comfortable.

More comfortable, it also has two good brothers, allowing developers to quickly insert HTML elements and Strings:

//Insert HTML element
document.body.insertAdjacentElement(
    'beforeend',
    document.createElement('a')
)

//Insert text
document.body.insertAdjacentText('afterbegin', 'cool!')

Move DOM elements

The sibling method insertAdjacentElement mentioned above can also be used to move existing elements. In other words, when the method is passed in to an element that already exists in the document, the 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>

Then operate it<h2>Get<h1>Back to:

const h1 = document.querySelector('h1')
const h2 = document.querySelector('h2')

h1.insertAdjacentElement('afterend', h2)

So we get this result:

<div class="first">
  <h1>Title</h1>
  <h2>Subtitle</h2>
</div>

<div class="second">
    
</div>

Replace DOM elements

Replacechild? This is the practice a few years ago. Whenever developers need to replace two DOM elements, they need to get their immediate parent elements in addition to the two necessary elements:

parentNode.replaceChild(newNode, oldNode)

Now, developers can use replacewith to replace two elements:

oldElement.replaceWith(newElement)

In terms of usage, it is fresher than the former.

It should be noted that:

  1. If the incoming newelement already exists in the document, the execution result of the method will be that the newelement is moved and the oldelement is replaced
  2. If the incoming newelement is a string, it will replace the original element as a textnode

Remove DOM elements

As with the old method of replacing elements, the old method of removing elements also needs to obtain the direct parent element of the target element:

const target = document.querySelector('#target')
target.parentNode.removeChild(target)

Now you only need to execute the remove method once on the target element:

const target = document.querySelector('#target')
target.remove()

Creating DOM elements with HTML strings

You must have noticed that the insertadjacent method mentioned above allows developers to insert a piece of HTML directly into the document. What if we just want to generate a DOM element for future use?

The parsefromstring method of the domparser object can meet this requirement. This method can transform a string of HTML or XML strings into a complete DOM document. That is to say, when we need to get the expected DOM element, we need to get the 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>')

Do a little DOM checker

The standard DOM API provides developers with many convenient ways to check the dom. For example, the matches method can determine whether an element matches a certain selector:

// <div class="say-hi">Hello DOM!</div>

const div = document.querySelector('div')

div.matches('div')      // true
div.matches('.say-hi')  // true
div.matches('#hi')      // false

The contains method can detect whether an element contains another element (or whether 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

One move: comparedocumentposition

Comparedocumentposition is a powerful API, which can quickly determine the location relationship of two DOM elements, such as: prior to, follow, and include. It returns an integer representing the relationship between two elements.

//Use the above example
container.compareDocumentPosition(h1)   // 20
h1.compareDocumentPosition(container)   // 10
h1.compareDocumentPosition(h2)          // 4
h2.compareDocumentPosition(h1)          // 2

Standard statement:

element.compareDocumentPosition(otherElement)

The return value is defined as follows:

  • 1: Two elements are no longer in the same document
  • 2: Other element before element
  • 4: Other element after element
  • 8: Other element contains element
  • 16: Otherelement is contained by element

So the question is, why is the result of the first line 20 and the result of the second line 10 in the above example?

Because H1 satisfies both “contained by container (16)” and “after container”, the execution result of the statement is 16 + 4 = 20. Similarly, the result of the second statement is 8 + 2 = 10.

DOM Observer: musionobserver

When dealing with user interaction, DOM elements of the current page usually change a lot. In some scenarios, developers need to monitor these changes and perform corresponding operations after triggering. Mutionobserver is a special interface provided by browser to monitor DOM changes. It is powerful enough to observe almost all changes of an element. Observable objects include: changes in text, addition and removal of child nodes, and changes in any element attribute.

As always, if you want to construct any object, new its constructor:

const observer = new MutationObserver(callback)

The constructor passed in is a callback function that will execute when the DOM element being monitored changes. Its two parameters are: the list musionrecords containing all changes this time and the observer itself. Among them, each piece of mutationrecords is a change record, which is a common object, including the following common properties:

  • Type: changed type, attributes / characterdata / childlist
  • Target: DOM element changed
  • Addednodes: NodeList composed of new child elements
  • Removednodes: NodeList of removed child elements
  • Attributename: the name of the attribute whose value has changed. If it is not an attribute change, null will be returned
  • Previoussibling: sibling before child element added or removed
  • Nextsibling: sibling after child element added or removed

According to the current information, you 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
      // ...
    }
  })
}

At this point, we have a DOM observer observer observer and a complete and available callback function after DOM changes, which is just one DOM element to be observed

const target = document.querySelector('#target')
observer.observe(target, {
    attributes: true,
  attributeFilter: ['class'],
  attributesOldValue: true,
  childList: true,
})

In the above code, we observe the DOM element with ID as target by calling the observe method of the observer object (the first parameter is the target element to be observed), and the second element, we pass in aConfiguration objects: enable observation on attribute / only observe class attribute / transfer old value of attribute when attribute changes / enable observation on sub element list.

The configuration object supports the following fields:

  • Attributes: Boolean, whether to listen for changes of element attributes
  • Attributefilter: String [], array of specific attribute names to listen to
  • Attributeoldvalue: Boolean, whether to record and pass the previous value of the listening element when its attribute changes
  • Characterdata: Boolean, whether to listen for changes in the character data contained in the node in the target element or sub element tree
  • Characterdataoldvalue: Boolean, whether to record and pass the previous value when the character data changes
  • Childlist: Boolean, whether to listen for the target element to add or remove child elements
  • Subtree: Boolean, whether to extend the monitoring range to all elements of the entire subtree under the target element

When the target element is no longer monitored, just call the disconnect method of the observer. If necessary, first call the takerecords method of the observer to delete all pending notifications from the notification queue of the observer and return them to an array composed of mutationrecord objects:

const mutationRecords = observer.takeRecords()
callback(mutationRecords)
observer.disconnect()

Don’t be afraid of DOM

Although most DOM API names are long (cumbersome to write), 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 point of view, they must provide a complete name to become clear and clear enough.

As long as these APIs can play their potential, what’s the point of tapping more keys?

DOM is an essential knowledge for every javscript developer because we use it almost every day. Don’t be afraid to be a senior DOM engineer as soon as possible.

Last

Scanning code to capture an interesting front-end er
DOM senior engineer incomplete Guide