Course: JavaScript

Progress (0%)

  1. Foundation

  2. Numbers

  3. Strings

  4. Conditions

  5. Loops

  6. Arrays

  7. Functions

  8. Objects

  9. Exceptions

  10. HTML DOM

  11. CSSOM

  12. Events

  13. Drag and Drop

  14. opt Touch Events

  15. Misc

  16. Project: Analog Clock

HTML DOM - The Node Interface

Chapter 45 43 mins

Learning outcomes:

  1. What is the Node interface
  2. The childNodes property
  3. The firstChild and lastChild properties
  4. The nextSibling and previousSibling properties
  5. The parentNode property
  6. The nodeName, nodeValue and nodeType properties
  7. Factory methods for creating nodes
  8. Adding nodes using appendChild() and insertBefore()
  9. Removing nodes using removeChild()
  10. Replacing nodes using replaceChild()
  11. Cloning nodes using cloneNode()
  12. The textContent property

Introduction

In the previous chapter, we learnt how to select elements from an HTML document using a handful of properties/methods on the document object, and by a couple of properties on given Element instances.

In this chapter, we shall now focus on nodes only. That is, we'll explore the Node interface in extreme detail. We shall see what's the Node interface in extreme detail. We shall see what's the Node interface meant for, what is it an abstract interface, what properties and methods does it come bundled with, and so on and so forth.

What is Node?

If we recall from the HTML DOM — Basics chapter, the Node interface is simply used to represent a node in the DOM tree.

It's an abstract interface which is extended by many other interfaces, such as Element, CharacterData, Document, DocumentType, and so on.

What's an abstract interface?

An abstract interface is one which couldn't be instantiated directly, but only extended by some other interface which can then be instantiated.

In our case, the Node interface being 'abstract' means that we couldn't have a direct Node instance — it must be extended by some other interface and then we could instantiate an instance from that particular derived interface.

For e.g. we can have Element instances in a document, so can we have CharacterData instances. Both of these interfaces extend Node.

But we can't have a direct Node instance.

The Node interface is extremely useful. It contains a great deal of properties and methods to help us operate on a given node in various ways.

Let's start with the properties:

PropertyPurpose
textContentThe textual content of the given node. Depends on the type of the node.
nodeTypeAn integer representing the type of the node.
nodeNameA string representing the name of the node.
nodeValueA string representing the value of the node.
parentNodeA reference to the parent node of the given node.
childNodesAn NodeList instance containing all the child nodes of the given node.
firstChildA reference to the first child node of the given node.
lastChildA reference to the last child of the given node.
nextSiblingA reference to the next sibling of the given node, i.e. the one that comes after it in the HTML source code.
previousSiblingA reference to the previous sibling of the given node, i.e. the one that comes before it in the HTML source code.
ownerDocumentA reference to the document object that owns the given node.

Note that all these properties are accessor properties.

To learn more about accessor properties, please refer to the chapter JavaScript Objects — Property Attributes.

Now, let's talk about the methods of the Node interface:

MethodPurpose
appendChild()Adds a node inside the calling node as its last child.
insertBefore()Adds a node inside the calling node before a reference child node.
replaceChild()Replace a particular child node inside the calling node with another node.
removeChild()Remove a particular child node from the calling node.
cloneNode()Make a copy of the calling node.

Apart from these, there are a total of 8 numeric constants defined on Node (and even Node.prototype) that represent given node types, each holding an integer from 1 to 8.

Not of all them are of use to us. The ones relatively more important are shown as follows:

ConstantValuePurpose
ELEMENT_NODE1Represents the element node type.
ATTRIBUTE_NODE2Represents the attribute node type.
TEXT_NODE3Represents the text node type.
COMMENT_NODE8Represents the comment node type.
DOCUMENT_NODE9Represents the (root) document node type.
DOCUMENT_TYPE_NODE10Represents the document-type node type.
DOCUMENT_FRAGMENT_NODE11Represents the document fragment node type.

We'll shortly see some examples where these constants are used to determine the types of arbitrary nodes.

Anyways, let's begin discussing about all the Node properties, starting with childNodes.

The childNodes property

The childNodes property of a given Node instance returns back a collection of all the children of the node in the DOM tree.

In particular, the collection returned is a NodeList instance.

First, let's understand what exactly is a NodeList.

What is a NodeList?

A NodeList object is very similar to an Array object.

It supports the usage of bracket notation to access individual items, and has a length property containing the total number of items.

For instance, if nodeList is a NodeList instance, then we could do something such as nodeList[0] to access to first item of the list, and similarly nodeList.length to determine the total number of items in it.

However, it's very important to note that a NodeList is NOT the same thing as an Array.

A NodeList instance doesn't have access to any array methods, such as indexOf(), push(), slice(), etc, simply because it's neither an array itself, nor does it inherit from the Array interface.

Alright, it's now time to consider an example.

<div id="main">
   <p>A paragraph</p>
   <!--A comment-->
</div>

Let's try to determine all the children of #main just by looking at this HTML code.

The first child is a text node, the second child is a <p> element, the third one is again a text node, the fourth one is a comment, and finally the last one is another text node. So, in total, we have 5 children.

Now, let's inspect the length of the childNodes property of this #main element:

var mainElement = document.getElementById('main');

console.log(mainElement.childNodes.length);
5

As expected, we have five items in the returned NodeList.

In the following code, we inspect each node in this list one-by-one, by logging its nodeName property, which we'll explore shortly below:

var mainElement = document.getElementById('main');

for (var i = 0, len = mainElement.childNodes.length; i < len; i++) {
   console.log(mainElement.childNodes[i].nodeName);
}
#text P #text #comment #text

Surely, we'll only be able to understand the output here once we get to know about the nodeName property, but just from natural intuition, we can still make some sense out of the output.

That is, the first node has the name #text and, likewise, might refer to a text node; the second one has the name P and, likewise, might refer to the <p> element; the third one is again a text node; the fourth one might refer to a comment node, and the last one is yet again another text node.

Simple.

Moving on, one thing very important to note regarding the NodeList instance returned by childNodes is that it's a live collection. That is, as we update the children of a given node, its childNodes property updates automatically.

We'll see what this menans later in this chapter, once we explore the DOM mutating methods appendChild(), insertBefore(), and removeChild().

The firstChild and lastChild properties

The firstChild and lastChild properties of a node return its first and last child, respectively.

If the node doesn't have any children at all, both these properties evaluate down to null.

Keep in mind that for a given node n, n.firstChild is functionally equivalent to n.childNodes[0] while n.lastChild is functionally equivalent to n.childNodes[n.childNodes.length - 1].

Obviously, these two properties, namely firstChild and lastChild, are really handy when we are only interested in the first or the last child of a given node, respectively, instead of manually accessing the child via childNodes.

As before, it's time for an example.

Here's the same HTML code as before:

<div id="main">
   <p>A paragraph</p>
   <!--A comment-->
</div>

And here we log the nodeName of the firstChild and the lastChild of #main:

var mainElement = document.getElementById('main');

console.log(mainElement.firstChild.nodeName);
console.log(mainElement.lastChild.nodeName);
#text #text

Quite simple.

The nextSibling and previousSibling properties

As we learnt in the chapter HTML DOM — Basics, the siblings of a given node are all the nodes that have the same parent as that node.

The Node interface provides us with two properties to retrieve the previous and the next sibling of a given node. They are, well, nextSibling and previousSibling, respectively.

For a given node, it's nextSibling returns sibling that appears immediately after it in the HTML markup. Similarly, previousSibling returns the sibling of the node that comes immediately before it in the HTML markup.

If there is no sibling after or before a given node in the HTML markup, nextSibling and previousSibling return null, respectively.

Let's consider an example.

Once again, below we have the same HTML as before:

<div id="main">
   <p>A paragraph</p>
   <!--A comment-->
</div>

Now, let's select #main's first child and retrieve its nextSibling:

var mainElement = document.getElementById('main');

console.log(mainElement.firstChild.nextSibling.nodeName);
P

As expected, it's the <p> element.

Now, let's select the last child of #main and then retrieve its previous sibling.

var mainElement = document.getElementById('main');

console.log(mainElement.lastChild.previousSibling.nodeName);
#comment

Once again, as expected, it's the comment node.

Time for a quick test.

What does the following code log?

var mainElement = document.getElementById('main');

console.log(mainElement.lastChild.nextSibling.nodeName);
  • #text
  • DIV
  • null
  • It throws an error
There is no sibling of the last child of #main that comes after it. Likewise, mainElement.lastChild.nextSibling resolves down to null. Thereafter, accessing the property nodeName on null leads to an error, as it's illegal to access any property on null.

The parentNode property

As the name suggests, the parentNode property of a Node instance returns back its parent node.

All nodes in the DOM tree have a parent, except for the root node, which is the document object. The parent node of document is null.

Time to consider an example.

Following we have the exact same HTML as before:

<div id="main">
   <p>A paragraph</p>
   <!--A comment-->
</div>

As we already know, this HTML snippet assumes that the markup is encapsulated inside the <body> tag.

Hence, assuming this notion, let's traverse all the way up from the first child of #main to document by following the parent node connections. At each stage, we log the nodeName of the current node:

var node = document.getElementById('main').firstChild;

console.log(node.nodeName);
console.log(node.parentNode.nodeName);
console.log(node.parentNode.parentNode.nodeName);
console.log(node.parentNode.parentNode.parentNode.nodeName);
console.log(node.parentNode.parentNode.parentNode.parentNode.nodeName);
#text DIV BODY HTML #document

Hopefully, it's very intuitive to follow along each of the logs in the console snippet above. It's just that we're going up from #main's first child to document — that's it.

We can make a fairly good hypothesis at this point that the parent node of the child node of a given element node n is equal to n.

Let's see whether or not this is really the case.

In the code below, we compare mainElement with the parent of main.firstChild using the === operator to see whether or not they both point to the exact same object in memory:

var mainElement = document.getElementById('main');

console.log(mainElement === mainElement.firstChild.parentNode);
true

And guess what — they really do.

Isn't this remarkable?

The nodeName property

The nodeName property of a given node holds a string containing the name of the node.

The name is dependent on the type of the node:

  • For text nodes, it's the string '#text'.
  • For comment nodes, it's the string '#comment'.
  • For element nodes, it's the name of the element's tag in uppercase, for e.g. 'SPAN' for a <span> element.
  • For document nodes, it's the string '#document'.
  • For document fragment nodes, it's '#document-fragment'.

There is a respective value for every single node type, but since the most used node types are the ones that we just discussed above, we'd let go off nodeName for other kinds of nodes.

Perhaps, the most important use case of nodeName is when we want to retrieve the tag name of an arbitrary element node, and determine exactly which element it is.

For example, in the code below, we check if the selected #main element is a <p> element. If it is, we log 'Yes' or else log 'No':

<div id="main">
   <p>A paragraph</p>
   <!--A comment-->
</div>
var mainElement = document.getElementById('main');

if (mainElement.nodeName === 'P') {
   console.log('Yes');
}
else {
   console.log('No');
}
No

Clearly, we know that #main is not a <p> element, but is rather a <div> element. Thereby, the log made is No.

Let's inspect the real value of mainElement.nodeName:

mainElement.nodeName
'DIV'

Since the #main element is a <div> element, mainElement.nodeName simply evaluates down to 'DIV'.

The nodeValue property

The nodeValue property helps us retrieve the value of a given node, especially text and comment nodes.

As with nodeName, nodeValue has a different return value based on the type of the underlying node:

  • For text nodes, it returns the textual content of the node.
  • For comment nodes, it operates similar to text nodes, returning back the textual content of the comment (excluding the comment tags).
  • For element nodes, it returns null.
  • For document nodes, it returns null.
  • For document fragment nodes, it returns null.

nodeValue is not that useful apart from the fact that we could use it to obtain the textual content of a given text or a comment node.

Consider the code below where we log the nodeValue of the <p> element, the nodeValue of its first (and only) child, which is a text node, and finally the nodeValue of the comment:

<div id="main">
   <p>A paragraph</p>
   <!--A comment-->
</div>
var mainElement = document.getElementById('main');
var paraElement = mainElement.childNodes[1];
var commentNode = mainElement.childNodes[3];

// nodeValue of the <p> element.
console.log(paraElement.nodeValue);

// nodeValue of the text inside <p>.
console.log(paraElement.firstChild.nodeValue);

// nodeValue of the comment node.
console.log(commentNode.nodeValue);
null A paragraph A comment

The nodeType property

The nodeType property is yet another, seemingly, useful property of the Node interface. It helps us determine the type of an arbitrary node.

It holds an integer that represents a particular node type based on the following table:

ValueMeaning
1Represents an Element node.
2Represents an Attr node.
3Represents a Text node.
4Represents a CDATASection node.
5Deprecated. Used to represent an entity reference node.
6Deprecated. Used to represent an entity node.
7Represents a ProcessingInstruction node of an XML document.
8Represents a Comment node.
9Represents a Document node.
10Represents a DocumentType node.
11Represents a DocumentFragment node.
12Deprecated. Used to represent a notation node.

The Node interface contains a couple of constants, each holding an integer representing one of these types. Thanks to these constants, we don't have to memorize each of these integers for the type it represents.

Here's the list of a couple of these constants:

ConstantValue
Node.ELEMENT_NODE1
Node.TEXT_NODE3
Node.COMMENT_NODE8
Node.DOCUMENT_NODE9
Node.DOCUMENT_TYPE_NODE10
Node.DOCUMENT_FRAGMENT_NODE11

As always, it's time to consider an example.

In the code below, we determine the node type of a couple of children of the #main element:

<div id="main">
   <p>A paragraph</p>
   <!--A comment-->
</div>
var mainElement = document.getElementById('main')
undefined
mainElement.childNodes[0].nodeType === Node.ELEMENT_NODE
false
mainElement.childNodes[1].nodeType === Node.ELEMENT_NODE
true
mainElement.childNodes[2].nodeType === Node.TEXT_NODE
true
mainElement.childNodes[3].nodeType === Node.COMMENT_NODE
true

Let's understand each of these logs:

  • Evidently, since the first child is a text node, and not an element node, the first log is false.
  • In contrast, since the second child is an element node, the second log is true.
  • The third child node is a text node and likewise the third log is true.
  • The fourth node is a comment, and so the fourth and the last log above is true.

Simple?

Now although nodeType does allow us to determine the precise type of an arbitrary Node instance, there are other ways to get this done as well.

Can you think of any? We've already covered one before.

Well, we can use the instanceof operator to test an arbitrary Node instance against a given constructor function.

If true is returned, it simply means that the type of the instance was indeed the one represented by the constructor function, or else it wasn't.

To learn more about constructor functions and the instanceof operator, please refer to the chapter JavaScript Objects — Constructors.

For instance, given a node instance n, we can use the expression n instanceof Element to check whether n is an Element node or not. Similarly, to check if n is a text node, we'd write n instanceof Text. And so on and so forth.

As a matter of fact, the nodeType accessor property itself, internally, uses instanceof to determine its corresponding integer value.

Likewise, whenever we need to test the type of an arbitrary Node instance against a specific class, we should stick to using instanceof with the constructor function representing that specific class.

Factory methods to create nodes

One of the most powerful ideas put forward by the DOM API is the ability to programmatically create new nodes. In this section, we shall cover how to create some of the most common nodes in the DOM.

The Document interface defines a couple of factory methods (i.e. methods that return objects) to create given types of nodes.

Here's a list of the most useful ones:

  • createElement()
  • createTextNode()
  • createDocumentFragment()

Let's first consider createElement().

createElement()

The createElement() method, available on the document object, creates a given element node.

It takes in a single string argument, specifying the tag name of the element to create, and then returns back an element node for that very element.

Here's its syntax:

document.createElement(tagName)

document is, obviously, the document object available in the global context, while tagName is the name of the element's tag that we ought to create.

The argument to createElement() is case-insensitive when we are dealing with HTML documents.

The return value of createElement() depends on the argument sent in. Talking generally, we could say that it returns back an Element instance, or more specifically, an HTMLElement instance (since we are only dealing with HTML documents for now).

For HTML documents, createElement() returns an instance belonging to some interface that inherits from HTMLElement, which in turn inherits from Element.

For example, calling node.createElement('div') returns back an HTMLDivElement instance. This interface, as we know from the last HTML DOM — Basics chapter, inherits from HTMLElement, which inherits from Element.

Simple?

In the code below, we create a <div> element node and set its innerHTML to 'A div node':

var divElement = document.createElement('div');
divElement.innerHTML = 'A div node';

Note that when we create a node, nothing happens on the current document. It's only when we add the node to the document that we get to witness any visible changes.

To add the node that we just created now, we'll use the Node interface's appendChild() method.

It operates very simply — the node that we provide to it is added as the last child inside the calling Element instance. We'll learn more about how appendChild() works in the next section below.

For now, let's consider a quick illustration of adding the node that we just instantiated:

Assuming that our HTML markup is as follows,

<div id="main"></div>

below we add the created divElement node inside #main:

var divElement = document.createElement('div');
divElement.innerHTML = 'A div node';

var mainElement = document.getElementById('main');

// Add the <div> element inside #main.
mainElement.appendChild(divElement);

Here's a live example:

Live Example

And voila! As soon as the HTML page loads, we see the text 'A div node' displayed. This confirms that our DOM <div> node really got added inside the given #main element.

createDocumentFragment()

The createDocumentFragment() factory method of the Document interface, as the name suggests, creates a document fragment node.

We'll cover document fragments in detail later in this unit.

A document fragment is basically just a very lightweight version of a document. We could use it as a temporary holding ground for a given set of nodes.

In the code below, we create a document fragment, then add two nodes inside it, followed by appending the whole fragment to the #main element:

<div id="main"></div>
var h1Element = document.createElement('h1');
h1Element.innerHTML = 'A heading';

var paraElement = document.createElement('p');
paraElement.innerHTML = 'A paragraph';

var fragment = document.createDocumentFragment();
fragment.appendChild(h1Element);
fragment.appendChild(paraElement);

var mainElement = document.getElementById('main');
mainElement.appendChild(fragment);

Live Example

The moment a DocumentFragment instance is detected by appendChild(), all the nodes inside the fragment are added to the calling node, leaving the fragment itself empty.

This can be confirmed by the following console snippet:

fragment.childNodes
NodeList []
fragment.childNodes.length
0

After the execution of the code above, all the nodes in fragment are dumped into #main, leaving fragment empty.

createTextNode()

Although, it's not used very much in the context of HTML documents, we can also add textual content to a document by creating text nodes manually and then appending them within element nodes.

In this regard, we have at our dispense the method createTextNode() on the Document interface.

Here's the syntax of createTextNode():

document.createTextNode(text)

text is a string and holds the textual content of the text node to be created. It's a required parameter; failing to provide a value to it would lead to an error.

Below shown is an example.

Here, we change the content of #main to 'A div node', by means of creating a separate text node with the help of document.createTextNode():

<div id="main"></div>
var mainElement = document.getElementById('main');

var textNode = document.createTextNode('A div node');

mainElement.appendChild(textNode);

Live Example

As is clear, manually creating text nodes and then appending them within given element nodes is quite a tedious activity.

A better approach is to keep using the innerHTML property whenever we need to add HTML content inside an element node, or maybe even the textContent property, as we shall explore later on in this chapter.

Adding nodes

appendChild()

The appendChild() method of the Node interface adds a given node inside the calling node, as its last child.

The reason it's called 'appendChild' is because it adds a given node at the end of the children of the calling node (i.e. the one that invoked appendChild()), which means that it becomes a child node of the calling node.

Here's the syntax of appendChild():

node.appendChild(childNode)

node is the node in which we want to add the childNode, right at its end.

Note that the childNode argument is required; failing to provide it will lead to an error.

Let's consider an example.

In the following code, we create a <p> element node, filled with some simple text, and then append it right inside the <body> element (whose id has been set to 'body'):

<body id="body">
   <h1>Learning the DOM</h1>
</body>
var paraElement = document.createElement('p');
paraElement.innerHTML = 'Learning <code>appendChild()</code>';

var bodyElement = document.getElementById('body');
bodyElement.appendChild(paraElement);

Live Example

Thanks to the innerHTML property, the <code> element used inside the string 'Learning <code>appendChild()</code>' gets parsed into a real <code> element node.

Moving on, if the given argument node to appendChild() is an existing node in the document, it's removed from its original location and then added to the new location.

This is illustrated in the following example:

<div id="sect1" style="border: 1px solid red">
   <h1>Section 1</h1>
   <p>Paragraph from section 1</p>
</div>

<div id="sect2" style="border: 1px solid blue">
   <h1>Section 2</h1>
   <p id="para">Paragraph from section 2</p>
</div>
var section1Element = document.getElementById('sect1');
var paraElement = document.getElementById('para');

section1Element.appendChild(paraElement);

Live Example

As we open up the link above, we notice that #sect1 has two paragraphs in it — one that was originally in it, while the other that was moved from #sect2.

The appendChild() method is a highly useful method in the DOM API. It's used frequently as compared to some other methods to add new elements to a given document simply because of its simplicity and amazing support across older browsers.

insertBefore()

insertBefore() is yet another method to add a given node to the document. Like its name, it's slightly different than appendChild().

Essentially, it's meant to insert a node right before a given node.

Here's its syntax:

node.insertBefore(newNode, childNode)

node is the node in which we want to add a new child node, newNode is that new node that we want to add, and childNode is the node that acts as a reference to specify exactly before which node do we want to add newNode.

Note that as with appendChild(), the arguments provided to insertBefore() are both meant to be child nodes of node. This helps us to remember the signature of insertBefore().

If childNode is null, it simply means that there is no reference that we could provide to the insertBefore() method before which to put newNode. In that case, insertBefore() simply adds newNode after the last child of node — similar to how appendChild() works.

In the following code, we add an <h3> element right before the <p> element in #main:

<div id="main">
   <h1>A heading</h1>
   <p>A paragraph</p>
</div>
var h3Element = document.createElement('h3');
h3Element.innerHTML = 'Another heading';

var mainElement = document.getElementById('main');

// Add an <h3> before the <p> element (the reference node).
mainElement.insertBefore(h3Element, mainElement.childNodes[3]);

Live Example

Since the node to be added is to be inside #main, we invoke insertBefore() on it. Moreover, since <h3> has to be added, it's the first argument to the method. Last but not the least, because the <h3> element ought to be added before <p>, <p> becomes the reference node.

Simple?

In the following example, we demonstrate the case when the last childNode argument to insertBefore() is null:

var h3Element = document.createElement('h3');
h3Element.innerHTML = 'Another heading';

var mainElement = document.getElementById('main');

// Add an <h3> at the end of #main
mainElement.insertBefore(h3Element, null);

Live Example

Notice how the <h3> element shows after the <p > element this time. It's still inside #main, but added right after its last child.

Removing nodes

Where adding elements to a document is a routine activity, removing elements is also not just an occasional one.

The removeChild() method of the Node interface allows us to remove a particular node from the document.

Its syntax is pretty straightforward:

node.removeChild(childNode)

node is the node whose child we wish to remove, while childNode is that particular child node. Failing to provide childNode would lead to an error.

Let's consider an example.

In the code below, we remove the <p> element from the #main element:

<div id="main">
   <h1>A heading</h1>
   <p>A paragraph</p>
</div>
var mainElement = document.getElementById('main');
var paraElement = mainElement.childNodes[3];

mainElement.removeChild(paraElement);

Live Example

Once paraElement is removed in the code above, it's no more a part of the DOM tree, but we can still access it as an individual element node.

Let's try doing this:

var mainElement = document.getElementById('main');
var paraElement = mainElement.childNodes[3];

mainElement.removeChild(paraElement);

console.log(paraElement.innerHTML);
A paragraph

Note that we can't just pick and choose an arbitrary node in the DOM and delete it, right away. We have to access its parent node and only then could we remove it using removeChild() on the parent node.

However, there is an easy trick that allows us to delete any arbitrary node, still using removeChild(). That we'll introduce right now.

In the code below, we select the desired <p> element (which now has an id for direct access) and then remove it without having to access the #main element separately:

<div id="main">
   <h1>A heading</h1>
   <p id="para">A paragraph</p>
</div>
var paraElement = document.getElementById('para');

paraElement.parentNode.removeChild(paraElement);

Live Example

The trick is to first access the node that we wish to remove, and then traverse up to its parent via its parentNode property. After this, we ought to call the removeChild() property on this parent node and then pass in the original node to it as an argument.

In general, to remove any arbitrary node node from a document, we can just call node.parentNode.removeChild(node).

Replacing nodes

In order to replace a node with another node, we can use the replaceChild() method.

It's important to note that the node that gets removed in the process must be a child of the node calling replaceChild().

Here's the method's syntax:

node.replaceChild(newNode, oldNode)

newNode is the node that we want to add, while oldNode is the node that we want to get replaced with newNode. node is the node inside which we want to make the replacement.

Both these arguments are required; failing to provide either will lead to an error.

As with insertBefore(), the first argument to replaceChild() is the new node to add.

Once again, akin to appendChild() and insertBefore(), the arguments to replaceChild() are both meant to be child nodes of node.

Always keep this thing in mind, i.e. the arguments to all four of these DOM mutating methods, on the Node interface, are meant to be child nodes of the calling Node instance.

Anyways, it's time for an example.

In the code below, we replace the <p> element with an <h3> element:

<div id="main">
   <h1>A heading</h1>
   <p>A paragraph</p>
</div>
var h3Element = document.createElement('h3');
h3Element.innerHTML = 'Another heading';

var mainElement = document.getElementById('main');

// Replace the <p> element with an <h3>.
mainElement.replaceChild(h3Element, mainElement.childNodes[3]);

Live Example

Piece of cake, isn't this?

The replaceChild() method can be implemented solely using removeChild() and insertBefore().

Cloning nodes

Recall our statement that if an existing node is passed to appendChild() (or insertBefore) as the node to be added, it would be removed from its original position in the document and then added to the new one.

Now what if we want to add a particular existing node inside another node but make sure that no repositioning occurs.

Well, it's quite simple in nature. We make a copy — or better to say, a clone — of the node to be added, and then pass this clone to the DOM mutationg method. This keeps the original node in its original position but adds an identical node to the new location.

So, it's a win-win situation!

But how to make a given node's clone? The answer is pretty much straightforward — using cloneNode().

Here's the syntax of cloneNode():

node.cloneNode([deepClone])

The method is called upon the node that we wish to clone.

The optional Boolean deepClone argument specifies whether or not deep cloning is desired, i.e. cloning the entire subtree following from node (including its children, and the children of these children, and so on).

By default, deepClone is false, in which case only the given node itself is cloned, including all its attributes.

However, it's more often that not changed to true when cloning element nodes, since these nodes have children nodes, and we typically want to copy the entire content of the element node, including these children.

Anyways, once done, cloneNode() returns back the clone.

The original node and its clone are identical in all their property values but are, at the end of the day, two distinct objects. That is, the === operator won't return true if we compare a node with its clone.

Cloning a node can be thought of as slicing an array from its start to its end. The original array and the sliced array both have the same data, yet they aren't the exact same objects in memory.

Time for an example.

In the code below, we clone the <p> element in #sect2 and then append the resulting clone inside #sect1:

<div id="sect1" style="border: 1px solid red">
   <h1>Section 1</h1>
   <p>Paragraph from section 1</p>
</div>

<div id="sect2" style="border: 1px solid blue">
   <h1>Section 2</h1>
   <p id="para">Paragraph from section 2</p>
</div>
var section1Element = document.getElementById('sect1');
var paraElement = document.getElementById('para');

var paraElementClone = paraElement.cloneNode(true); section1Element.appendChild(paraElementClone);

Live Example

Notice the true argument supplied to cloneNode() here. This is done because we want all the content inside the <p> element to be copied, not just the element itself.

Recall that there is a text node inside <p> holding the text 'Paragraph from section 2'. We want this node to be copied as well.

The textContent property

The textContent property's name is quite self-explanatory in its purpose. That is, it's meant to represent all the textual content inside a given node.

As with nodeValue, textContent behaves differently based on the type of the underlying node:

  • For text nodes, it's identical to nodeValue.
  • For comment nodes, it's identical to nodeValue.
  • For element nodes and document fragment nodes, it holds the concatenation of the textContent of each of its child nodes, excluding comment nodes.
  • For document nodes, it holds the value null.

As we shall see in the chapter HTML DOM — Elements, using textContent is much more better performance-wise than innerHTML and innerText if only what we're interested in is to change the textual content inside a given element.

Let's see an example.

In the code below, we retrieve the textContent of a couple of nodes from the given HTML document:

<div id="main">
   <p>A paragraph</p>
   <!--A comment-->
</div>
var mainElement = document.getElementById('main');

console.log(mainElement.textContent);
console.log(mainElement.childNodes[1].textContent);
console.log(mainElement.childNodes[3].textContent);
A paragraph A paragraph A comment

As can be seen in the first log, the text content of the #main element is a little bit weird, but for good reason. All the whitespace text nodes in #main show up in its textContent property to ultimately give a string with long sequences of whitespace, just as they appear in the HTML source code.

textContent can also be assigned a value. In that case, it removes all the children of the given node, if it has any, and creates a single child text node in it, if the underlying node can have children at all.

For example, consider the code below:

<div id="main">
   <p>A paragraph</p>
   <!--A comment-->
</div>
var mainElement = document.getElementById('main');

console.log('Before:', mainElement.childNodes.length);

// Empty the #main element.
mainElement.textContent = '';

console.log('After:', mainElement.childNodes.length);
Before: 5 After: 0

We set the textContent of #main to '' which has the effect that the childNodes list of #main now holds absolutely nothing.

Since text and comment nodes can't have children in the first place, setting their textContent property won't have any effect on their childNodes at all.