Node properties: type, tag and contents
You already know the DOM is a tree of nodes. Now look closer at the nodes themselves: what kind of objects they are, and which properties you reach for every day to read a node’s type, its tag, and the content inside it.
DOM node classes
Not every node carries the same properties. The object standing in for an <a> tag has link properties like href. The object for an <input> carries input properties like value and type. A text node has neither — it is a different kind of thing entirely. Yet all of them share a common floor of properties and methods, because every DOM node class descends from one hierarchy.
Each node is an instance of a specific built-in class. The root of the family is EventTarget. Node inherits from it, and everything else branches off from there.
Here is what each class in that chain does:
-
EventTarget is the abstract root. No object is ever created directly from it. Its whole job is to give every node the ability to receive events, which you’ll study later.
-
Node is also abstract, never instantiated on its own. It supplies the core tree navigation —
parentNode,nextSibling,childNodes, and friends (all getters). The concrete node classes inherit these. -
Document represents the document as a whole. For historical reasons browsers often expose it through
HTMLDocument, though the current spec no longer requires that layer. The globaldocumentobject is an instance of this class and serves as your entry point into the DOM. -
CharacterData is another abstract class, split into two concrete ones:
-
Element is the base class for elements. It adds element-only navigation like
nextElementSiblingandchildren, plus search methods likegetElementsByTagNameandquerySelector. Browsers render more than HTML — XML and SVG too — soElementbranches intoSVGElement,XMLElement(not our concern here), andHTMLElement. -
HTMLElement is the base for every HTML element, and where you’ll spend most of your time. It’s inherited by the concrete per-tag classes:
- HTMLInputElement for
<input>, - HTMLBodyElement for
<body>, - HTMLAnchorElement for
<a>, - and so on down the list.
- HTMLInputElement for
Many tags get their own class with extra properties, but not all. Plain containers like <span>, <section>, and <article> add nothing special, so they’re plain HTMLElement instances.
The set of properties a node exposes is the sum of everything along its inheritance chain. Take the object for an <input>. It belongs to HTMLInputElement, and it stacks up like this:
value, type, checked…
hidden, innerText, dataset…
innerHTML, querySelector…
parentNode, childNodes…
addEventListener…
hasOwnProperty, toString…
Reading top to bottom: HTMLInputElement adds input-specific properties, HTMLElement adds common HTML element behavior, Element adds generic element methods, Node adds tree properties, EventTarget adds event support, and finally everything inherits from plain Object, so hasOwnProperty and the rest are there too.
Want to see a node’s class name at runtime? Every object has a constructor property pointing at its class, and constructor.name is the name string:
alert( document.body.constructor.name ); // HTMLBodyElement
Or just coerce it to a string:
alert( document.body ); // [object HTMLBodyElement]
You can also test the chain with instanceof. Because document.body sits at the bottom of a long inheritance line, it answers true to every class above it:
alert( document.body instanceof HTMLBodyElement ); // true
alert( document.body instanceof HTMLElement ); // true
alert( document.body instanceof Element ); // true
alert( document.body instanceof Node ); // true
alert( document.body instanceof EventTarget ); // true
None of this is a special DOM-only mechanism. DOM nodes are ordinary JavaScript objects using ordinary prototype-based inheritance. Log one with console.dir(elem) in a browser and you can walk down HTMLElement.prototype, Element.prototype, and so on in the console.
The “nodeType” property
nodeType is the old-school way to ask what kind of node you’re holding. It predates the class-based checks and returns a number:
elem.nodeType == 1for element nodes,elem.nodeType == 3for text nodes,elem.nodeType == 9for the document object,- plus a handful of other values listed in the specification.
element
<body>, <div>…
text
“Hello”
document
document
An example that inspects three nodes in one document:
<body>
<script>
let elem = document.body;
// what type of node is in elem?
alert(elem.nodeType); // 1 => element
// and its first child is...
alert(elem.firstChild.nodeType); // 3 => text
// the document object itself reports 9
alert( document.nodeType ); // 9
</script>
</body>
In modern code the class-based tests (instanceof, or checking tagName) are usually clearer, but nodeType can be the shorter tool when you just need “is this an element or a text node?”. Note it’s read-only — you can’t reassign a node’s type.
Tag: nodeName and tagName
Given a node, two properties report its tag name: nodeName and tagName.
alert( document.body.nodeName ); // BODY
alert( document.body.tagName ); // BODY
So why two? The difference is small but real, and it comes down to which class defines each:
tagNameexists only onElementnodes — it comes from theElementclass.nodeNameis defined on everyNode:- for elements it returns the same string as
tagName, - for other node types (text, comment, document) it returns a label describing what the node is.
- for elements it returns the same string as
| node | tagName | nodeName |
|---|---|---|
| <body> | BODY | BODY |
| comment | undefined | #comment |
| document | undefined | #document |
Here’s that table proven in code, comparing a comment node and the document:
<body><!-- comment -->
<script>
// for the comment node
alert( document.body.firstChild.tagName ); // undefined (not an element)
alert( document.body.firstChild.nodeName ); // #comment
// for the document
alert( document.tagName ); // undefined (not an element)
alert( document.nodeName ); // #document
</script>
</body>
If your code only ever touches elements, the two are interchangeable — reach for whichever reads better.
Pick a node below and watch all three properties at once. Notice how tagName reports undefined for the comment and the document, while nodeName always has an answer:
innerHTML: the contents
The innerHTML property gives you the HTML inside an element, as a string. Read it, or assign to it — writing to innerHTML is one of the most direct ways to rewrite part of the page.
This example reads the body’s current markup, then blows it away and replaces it:
<body>
<p>A paragraph</p>
<div>A div</div>
<script>
alert( document.body.innerHTML ); // read the current contents
document.body.innerHTML = 'The new BODY!'; // replace it
</script>
</body>
Try it live. Edit the markup, set it on the target, and see the read-back of innerHTML afterwards — note that the browser may normalize what you typed:
Feed it broken markup and the browser cleans up after you. A missing closing tag gets closed automatically:
<body>
<script>
document.body.innerHTML = '<b>test'; // forgot to close the tag
alert( document.body.innerHTML ); // <b>test</b> (fixed)
</script>
</body>
Beware: “innerHTML+=” does a full overwrite
You can seemingly append markup with elem.innerHTML += "more html":
feedDiv.innerHTML += "<div>New reply<img src='avatar.png'/> !</div>";
feedDiv.innerHTML += "Tap to open";
Be careful. This is not an append — it’s a complete rebuild. The += operator expands to a read followed by a full assignment:
elem.innerHTML += "...";
// is shorthand for:
elem.innerHTML = elem.innerHTML + "..."
So each += runs two steps:
in the DOM
resources reloaded
- The old contents are removed.
- Fresh HTML — the old string concatenated with the new — is parsed and written in its place.
Because the content is zeroed out and rebuilt from a string, every image and other resource inside gets reloaded.
In the feedDiv example, feedDiv.innerHTML += "Tap to open" re-creates the whole subtree and re-fetches avatar.png (hopefully it’s cached, which depends on the response’s HTTP caching headers). With a lot of existing text and images, that reload flicker becomes obvious.
There are quieter side effects too. If the user had text selected inside the element, most browsers drop the selection when innerHTML is rewritten. If there was an <input> with typed-in text, that text is gone. And so on down the list.
Better tools exist for genuine appending, and you’ll meet them shortly in the chapter on modifying the document.
outerHTML: full HTML of the element
outerHTML is innerHTML plus the element’s own tag — the complete HTML of the element, wrapper included.
<div id="elem">Draft <b>copy</b></div>
<script>
alert(elem.outerHTML); // <div id="elem">Draft <b>copy</b></div>
</script>
Reading it is unremarkable. Writing to it has a surprise: assigning to outerHTML does not modify the element. It replaces the element in the DOM and leaves your original object untouched.
That’s odd enough to earn its own warning. Look closely:
<div>Draft copy</div>
<script>
let div = document.querySelector('div');
// replace div.outerHTML with <p>...</p>
div.outerHTML = '<p>Final copy</p>'; // (*)
// 'div' is still the same!
alert(div.outerHTML); // <div>Draft copy</div> (**)
</script>
On line (*) the page swaps: where the <div> was, the DOM now shows <p>Final copy</p>. But line (**) still reports the old <div> markup. The div variable never changed.
Here’s the mental model. The assignment doesn’t reach into the object div points at and edit it. Instead it detaches that object from the document and drops the newly parsed HTML into the gap:
(detached, unchanged)
Step by step, div.outerHTML = '<p>Final copy</p>' did this:
divwas removed from the document.- The parsed
<p>A new element</p>took its place. - The
divvariable kept its old value. The new element wasn’t saved to any variable.
The trap is easy to fall into: overwrite elem.outerHTML, then keep working with elem as though it now holds the new content. It doesn’t. That intuition is right for innerHTML but wrong for outerHTML. If you need a reference to the replacement, query the DOM for it after the assignment.
nodeValue/data: text node content
innerHTML is an Element-only property. Text and comment nodes aren’t elements, so they don’t have it. Their equivalent is a pair of properties: nodeValue and data. For everyday use the two are interchangeable — the spec draws only minor distinctions between them — so we’ll use data because it’s shorter.
Reading the content of a text node and a comment node:
<body>
Hello
<script>
let text = document.body.firstChild;
alert(text.data); // Hello
let comment = text.nextSibling;
alert(comment.data); // Comment
</script>
</body>
Reading a text node’s content makes obvious sense. But why would you read a comment? Because comments can carry hidden instructions. Some tools embed template directives inside them:
<div>Book now<!-- track: cta-top --></div>
JavaScript can pull those out through data and act on the embedded instructions.
textContent: pure text
textContent gives you the text inside an element with every tag stripped out — text only, no markup.
<div id="recipe">
<h1>Lemon Cake</h1>
<p>Bake for 40 minutes.</p>
</div>
<script>
// Lemon Cake Bake for 40 minutes.
alert(recipe.textContent);
</script>
The tags are gone; the text that lived inside them remains, concatenated.
Reading textContent is only occasionally useful. Writing to it is where the property earns its keep, because it inserts text the safe way.
Say you have an arbitrary string — something a user typed — and you want to display it. Your choice of property decides how the browser treats it:
- Assign it to
innerHTMLand the string is parsed as HTML. Any tags in it become real elements. - Assign it to
textContentand the string is treated as literal text. Every character shows exactly as written.
The code behind that figure:
<div id="elem1"></div>
<div id="elem2"></div>
<script>
let name = prompt("What's your name?", "<b>Maya</b>");
elem1.innerHTML = name;
elem2.textContent = name;
</script>
- The first
<div>receives the name as HTML: the<b>tag renders, so you see a bold name. - The second
<div>receives it as text, so you literally see<b>Maya</b>on the page.
Type anything into the field below — include some tags — and compare the two panels. The innerHTML panel parses your text as markup; the textContent panel shows every character exactly as written:
Most of the time, data coming from a user should stay text — you don’t want their input injecting HTML into your page. Assigning to textContent does exactly that, and it’s a first line of defense against injection.
The “hidden” property
The hidden attribute — and its matching DOM property — controls whether an element is shown. Set it in HTML or from JavaScript:
<div>Both divs below are hidden</div>
<div hidden>With the attribute "hidden"</div>
<div id="elem">JavaScript assigned the property "hidden"</div>
<script>
elem.hidden = true;
</script>
Functionally hidden matches style="display:none", just shorter to write and clearer in intent.
A tiny demo: toggle the property by hand, or flip it on a timer to make the element blink. Watch the live read-out of box.hidden as it changes:
More properties
Beyond the shared set, elements carry properties that depend on their specific class:
value— the current value of an<input>,<select>, or<textarea>(HTMLInputElement,HTMLSelectElement, and so on).href— the destination of<a href="...">(HTMLAnchorElement).id— theidattribute, available on every element (HTMLElement).- …and many more.
For example:
<input type="text" id="elem" value="value">
<script>
alert(elem.type); // "text"
alert(elem.id); // "elem"
alert(elem.value); // value
</script>
Most standard HTML attributes have a matching DOM property you can read and write like this.
To find every property a class supports, the specification is the authority — HTMLInputElement, for instance, is documented at https://html.spec.whatwg.org/#htmlinputelement. For a quick look, or to see what a specific browser exposes, log the element with console.dir(elem) and browse, or open the “Properties” pane in the Elements tab of your dev tools.
Summary
Every DOM node is an instance of a class, and those classes form an inheritance hierarchy. A node’s full set of properties and methods is the accumulation of that entire chain.
The core node properties:
nodeTypeTells you whether a node is an element, text, or something else, as a number:
1for elements,3for text nodes,9for the document, plus a few others. Read-only.
nodeName/tagNameThe tag name for elements (uppercase outside XML mode). For non-element nodes,
nodeNamereturns a label describing the node whiletagNameisundefined. Read-only.
innerHTMLThe HTML markup inside an element, as a string. Readable and writable.
outerHTMLThe full HTML of the element, its own tag included. Writing to
elem.outerHTMLleaveselemuntouched — it replaces the element in the document instead.
nodeValue/dataThe content of a non-element node such as text or a comment. The two are near-identical;
datais the shorter one to reach for. Writable.
textContentThe text inside an element with all tags removed. Writing to it inserts a string as literal text — special characters and tag syntax are shown verbatim, never parsed — which is the safe way to display user-supplied content.
hiddenSet it to
trueto hide the element, equivalent to CSSdisplay:none.
Nodes also carry class-specific properties: <input> (HTMLInputElement) has value and type, <a> (HTMLAnchorElement) has href, and so on. Most standard HTML attributes have a corresponding DOM property.
But attributes and properties are not always the same thing, and the difference between them is the subject of the next chapter.