When jQuery was released, one of the main reasons behind its meteoric rise to popularity was the ease with which it could select DOM elements, traverse them and modify their content. But that was way back in 2006. In those days we were stuck with Internet Explorer 7 and ECMAScript 5 was still a couple of years off.
Luckily, a lot has changed since then. Browsers have become considerably more standards compliant and native JavaScript has improved in leaps and bounds. And as things have improved, we have seen people questioning whether we still need jQuery. I’m not going to get into that argument here, rather I’d like to offer some food for thought, as I present six native DOM manipulation methods which were inspired by this great library. These are as follows:
In this article I want to examine the similarities and the differences between these native DOM manipulation methods and their jQuery counterparts. Then hopefully, the next time you find yourself including jQuery for the sake of a convenience method or two, you might opt to embrace the power of vanilla JavaScript instead.
1. append()
The append method performs an insertion operation, that is, it adds nodes to the DOM tree. As the name indicates, it appends the passed arguments to the list of children of the node on which it is invoked. Consider the following example:
const parent = document.createElement('div')
const child1 = document.createElement('h1')
parent.append(child1)
parent.outerHTML
// <div><h1></h1></div>
const child2 = document.createElement('h2')
parent.append(child2)
parent.outerHTML
// <div><h1></h1><h2></h2></div>
At this point you would be forgiven for asking how this differs from the native appendChild method. Well, a first distinction is that append()
can take multiple arguments at once, and the respective nodes will be appended to the list of children, just like the jQuery append method. Continuing the previous snippet:
const child3 = document.createElement('p')
const child4 = document.createElement('p')
const child5 = document.createElement('p')
parent.append(child3, child4, child5)
parent.outerHTML
/* Outputs:
<div>
<h1></h1>
<h2></h2>
<p></p>
<p></p>
<p></p>
</div>
*/
Furthermore, an argument can be even a string. So, while with appendChild()
a rather verbose syntax must be employed:
parent.appendChild(document.createTextNode('just some text'))
with append()
the same operation is shorter:
parent.append('just some text')
parent.outerHTML
/* Outputs:
<div>
<h1></h1>
<h2></h2>
<p></p>
<p></p>
<p></p>
just some text
</div>
*/
The string is converted to a Text node, so any HTML is not parsed:
parent.append('<p>foo</p>')
parent.outerHTML
/* Outputs:
<div>
<h1></h1>
<h2></h2>
<p></p>
<p></p>
<p></p>
just some text
<p>foo</p>
</div>
*/
This is in contrast to the jQuery method, where strings of markup are parsed and the corresponding nodes are generated and inserted into the DOM tree.
As is usually the case, if the appended node it is already present in the tree, it is first removed from its old position:
const myParent = document.createElement('div')
const child = document.createElement('h1')
myParent.append(child)
const myOtherParent = document.createElement('div')
const myOtherParent.append(child)
myOtherParent.outerHTML
// <div><h1></h1></div>
myParent.outerHTML
// <div></div>"
A final difference between append()
and appendChild()
is that the latter returns the appended node, whereas the former returns undefined
.
2. prepend()
The prepend method is very similar to append()
. Children are added, but this time they are prepended to the list of children of the node on which the method is called, just before the first child:
const parent = document.createElement('div')
const child1 = document.createElement('p')
parent.prepend(child1)
parent.outerHTML
// <div><p></p></div>
const child2 = document.createElement('h2')
parent.prepend('just some text', child2)
parent.outerHTML
/* Outputs:
<div>
just some text
<h2></h2>
<p></p>
</div>
*/
The return value of the method is undefined
. The corresponding jQuery method is prepend().
3. after()
The after method is another insertion method, but this time it must be called on a child node, that is, a node with a definite parent. Nodes are inserted as adjacent siblings, as can be seen in the following example:
const parent = document.createElement('ul')
const child = document.createElement('li')
child.append('First item')
parent.append(child)
child.after(document.createElement('li'))
parent.outerHTML
// <ul><li>First item</li><li></li></ul>
The return value is undefined
and in jQuery the similar operation is after().
4. before()
The before method is similar to after()
, but now the nodes are inserted before the child node:
const parent = document.createElement('ul')
const child = document.createElement('li')
child.append('First item')
parent.append(child)
const child1 = document.createElement('li')
child1.append('Second item')
const child2 = document.createElement('li')
child2.append('Third item')
child.before(child1, child2)
parent.outerHTML
/* Outputs:
<ul>
<li>Second item</li>
<li>Third item</li>
<li>First item</li>
</ul>
*/
Once again, the return value is undefined.
The respective jQuery method is before().
5. replaceWith()
Suppose we wanted to replace one DOM node with another. Of course, they might have children, so this operation would substitute entire DOM subtrees. Before the introduction of this set of convenience methods, we would have used replaceChild():
const parent = document.createElement('ul')
parent.innerHTML = `
<li>first</li>
<li>second</li>
<li>third</li>
`
parent.outerHTML
// <ul><li>first</li><li>second</li><li>third</li></ul>"
const secondChild = parent.children[1]
const newSecondChild = document.createElement('li')
newSecondChild.innerHTML = '<a href="#">second</a>'
secondChild.parentNode.replaceChild(newSecondChild, secondChild)
parent.outerHTML
/* Outputs:
<ul>
<li>first</li>
<li><a href="#">second</a></li>
<li>third</li>
</ul>
*/
(innerHTML and template literals were used to ease the tree’s construction)
The same operation can be executed with replaceWith in a much less verbose manner:
parent = document.createElement('ul')
parent.innerHTML = `
<li>first</li>
<li>second</li>
<li>third</li>
`
secondChild = parent.children[1]
newSecondChild = document.createElement('li')
newSecondChild.innerHTML = '<a href="#">second</a>'
secondChild.replaceWith(newSecondChild)
Apart from the shorter syntax, a feature of this newer method is that it accepts several arguments, allowing to substitute a node with a list of other nodes. Continuing the previous interactive JavaScript session:
const newerSecondChild = document.createElement('li')
newerSecondChild.append('another item')
const newThirdChild = document.createElement('li')
newThirdChild.append('yet another item')
newSecondChild.replaceWith(newerSecondChild, newThirdChild)
parent.outerHTML
/* Outputs:
<ul>
<li>first</li>
<li>another item</li>
<li>yet another item</li>
<li>third</li>
</ul>
*/
Here too, the return value of the method is undefined
. You can compare this with the homonym jQuery method.
remove()
What about removing nodes from the DOM tree? The ‘old’ method is removeChild(). As indicated by its name, it must be called on the parent of the node n
to be deleted:
n.parentNode.removeChild(n)
However, with remove(), the operation is considerably more straightforward:
const parent = document.createElement('ul')
const n = document.createElement('li')
parent.append(n)
parent.outerHTML
// <ul><li></li></ul>
n.remove()
parent.outerHTML
// <ul></ul>
A difference with the analogue operation in jQuery, is how event listeners attached to the removed node are handled. jQuery removes all bound events and data associated with the element, while the native method doesn’t touch the event listeners:
const parent = document.createElement('ul')
const n = document.createElement('li')
parent.append(n)
n.addEventListener('test', console.log.bind(console))
const e = new Event('test')
n.dispatchEvent(e)
Event {isTrusted: false, type: "test", ...
n.remove()
n.dispatchEvent(e)
Event {isTrusted: false, type: "test", ...
This behavior is more similar to the jQuery detach method.
Browser Support
At the time of writing, the support status for the first five convenience methods — prepend()
, append()
, before()
, after()
and replaceWith()
— on desktop browsers is as follows:
- Chrome implements them, as of version 54.
- Firefox supports them, as of version 49.
- Safari supports them, as of version 10.
- Opera supports them, as of version 41.
- Disappointingly, they are not supported in Internet Explorer, nor Microsoft Edge (although for Edge, the feature is in development.
The remove
method enjoys wider support. Microsoft Edge implements it since version 14.
For those browsers that don’t yet provide these methods, several polyfills are available. childNode.js is one of them, and other polyfills can be found on the MDN pages dedicated to these methods, already cited in this article.
7. Bonus Method: insertAdjacentHTML
Before concluding, a few words about insertAdjacentHTML. It provides insertion operations similar to the first four methods listed above — append()
, prepend()
, after()
, before()
— and content to be added is specified with a string of HTML:
const parent = document.createElement('div')
parent.insertAdjacentHTML('beforeend', '<p>A paragraph</p>')
parent.insertAdjacentHTML('beforeend', '<p>Another paragraph</p>')
parent.insertAdjacentHTML('afterbegin', '<p>Yet another paragraph</p>')
const grandParent = document.createElement('div')
grandParent.append(parent)
parent.insertAdjacentHTML('afterend', '<div class="after"></div>')
parent.insertAdjacentHTML('beforebegin', '<div class="before"></div><div class="before2"></div>')
grandParent.outerHTML
/* Outputs:
<div>
<div class="before"></div>
<div class="before2"></div>
<div>
<p>Yet another paragraph</p>
<p>A paragraph</p>
<p>Another paragraph</p>
</div>
<div class="after"></div>
</div>
*/
Note how we had to make the parent
node a child of another node to be able to use the position arguments beforebegin
and afterend
.
Luckily, insertAdjacentHTML()
is available everywhere.
Conclusion
And now we are at the end of this brief overview of these jQuery-inspired DOM methods. I hope that in the course of this article, I have demonstrated how the native DOM API is progressing and how these native methods can often simply replace their jQuery counterparts.
But what do you think? Does this go any way to breaking your jQuery dependency? Or is the lack of IE support a deal breaker? I’d love to hear from you in the comments below.
This article was peer reviewed by Sebastian Seitz. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!