1.1 DOM is a tree structure
When you write down the following structure in HTML, the browser will parse it into a DOM tree:
<!DOCTYPE html> <html lang="en"> <head> <title>HTML</title> </head> <body> <!-- Add your content here--> </body> </html>
On the left you see the HTML document in its tree form. And on the right you see the corresponding JavaScript object that represents the selected element on the left. For example, the selected <body> element highlighted in blue, is an element node and an instance of the HTMLBodyElement interface.
On the left you will see the DOM tree structure of the HTML, and on the right you will see the relevant js objects that we can manipulate, such as the highlighted body element being an instance object of the HTML BodyElement.
What you should take away here is that html documents get parsed by a browser and converted into a tree structure of node objects representing a live document. The purpose of the DOM is to provide a programatic interface for scripting (removing, adding, replacing, eventing, modifiying) this live document using JavaScript.
The following are the most common types of nodes: (eg means, for example)
- DOCUMENT_NODE (e.g. window.document)
- ELEMENT_NODE (e.g. <body>, <a>, <p>, <script>, <style>, <html>, <h1> etc...)
- ATTRIBUTE_NODE (e.g. class="funEdges")
- TEXT_NODE (e.g. text characters in an html document including carriage returns and white space)
- DOCUMENT_FRAGMENT_NODE (e.g. document.createDocumentFragment())
- DOCUMENT_TYPE_NODE (e.g. <!DOCTYPE html>)
These are properties of Node objects whose values are numbers:
<!DOCTYPE html> <html lang="en"> <body> <script> console.log(Node.ELEMENT_NODE) //logs 1, one is the numeric code value for element nodes </script> </body> </html>
We can iteratively get the properties and values of all Node objects in the following way:
<!DOCTYPE html> <html lang="en"> <body> <script> for(var key in Node){ console.log(key,' = '+Node[key]); }; /* the above code logs to the console the following ELEMENT_NODE = 1 ATTRIBUTE_NODE = 2 TEXT_NODE = 3 CDATA_SECTION_NODE = 4 ENTITY_REFERENCE_NODE = 5 ENTITY_NODE = 6 PROCESSING_INSTRUCTION_NODE = 7 COMMENT_NODE = 8 DOCUMENT_NODE = 9 DOCUMENT_TYPE_NODE = 10 DOCUMENT_FRAGMENT_NODE = 11 NOTATION_NODE = 12 DOCUMENT_POSITION_DISCONNECTED = 1 DOCUMENT_POSITION_PRECEDING = 2 DOCUMENT_POSITION_FOLLOWING = 4 DOCUMENT_POSITION_CONTAINS = 8 DOCUMENT_POSITION_CONTAINED_BY = 16 DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 32 */ </script> </body> </html>
for(var i in Node){console.log(i + " " +Node[i])} VM360:2 ELEMENT_NODE 1 VM360:2 ATTRIBUTE_NODE 2 VM360:2 TEXT_NODE 3 VM360:2 CDATA_SECTION_NODE 4 VM360:2 ENTITY_REFERENCE_NODE 5 VM360:2 ENTITY_NODE 6 VM360:2 PROCESSING_INSTRUCTION_NODE 7 VM360:2 COMMENT_NODE 8 VM360:2 DOCUMENT_NODE 9 VM360:2 DOCUMENT_TYPE_NODE 10 VM360:2 DOCUMENT_FRAGMENT_NODE 11 VM360:2 NOTATION_NODE 12 VM360:2 DOCUMENT_POSITION_DISCONNECTED 1 VM360:2 DOCUMENT_POSITION_PRECEDING 2 VM360:2 DOCUMENT_POSITION_FOLLOWING 4 VM360:2 DOCUMENT_POSITION_CONTAINS 8 VM360:2 DOCUMENT_POSITION_CONTAINED_BY 16 VM360:2 DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC 32 VM360:2 toString function toString() { [native code] }
In the following table, I list some common node types and their corresponding values. Looking at this table, I want you to understand that nodeType is just the type we use to classify the current node type. It is just a number that has no special meaning, such as whether the nodeType of HTML BodyElement representsIt is of type ELEMENT_NODE.
Note that ATTRIBUTE_NODE will be removed from the DOM 4 specification.
1.3 Node Inheritance
- Object < Node < Element < HTMLElement < (e.g. HTML*Element)
- Object < Node < Attr (This is deprecated in DOM 4)
- Object < Node < CharacterData < Text
- Object < Node < Document < HTMLDocument
- Object < Node < DocumentFragment
The translator also experimented with:
To verify that all nodes inherit from Node, let's look at the following code:
<!DOCTYPE html> <html lang="en"> <body> <a href="#">Hi</a> <!-- this is a HTMLAnchorElement which inherits from... --> <script> //Get Nodes var nodeAnchor = document.querySelector('a'); //Create an empty array to hold all properties var props = []; //loop over element node object getting all properties & methods (inherited too) for(var key in nodeAnchor){ props.push(key); } //log alphabetical list of properties & methods console.log(props.sort()); </script> </body> </html>
Translator: I don't think the code above the author can prove what he said, so it's better to have another code like this:
<!DOCTYPE html> <html lang="en"> <body> <a href="#">Hi</a> <!-- this is a HTMLAnchorElement which inherits from... --> <script> var props = []; for(var key in Node){ props.push(key); } //log alphabetical list of properties & methods console.log(props.sort()); </script> </body> </html>
Note: You can extend DOM objects, but it is generally not recommended to extend host objects.
Properties and methods of 1.4 nodes
Node property:
- childNodes
- firstChild
- lastChild
- nextSibling
- nodeName
- nodeType
- nodeValue
- parentNode
- previousSibling
Node Methods:
- appendChild()
- cloneNode()
- compareDocumentPosition()
- contains()
- hasChildNodes()
- insertBefore()
- isEqualNode()
- removeChild()
- replaceChild()
Document Methods:
- document.createElement()
- document.createTextNode()
HTML * Element Properties:
- innerHTML
- outerHTML
- textContent
- innerText
- outerText
- firstElementChild
- lastElementChild
- nextElementChild
- previousElementChild
- children
HTML element Methods:
- insertAdjacentHTML()
1.5 Determining the type and value of a node
<!DOCTYPE html> <html lang="en"> <body> <a href="#">Hi</a> <script> //This is DOCUMENT_TYPE_NODE or nodeType 10 because Node.DOCUMENT_TYPE_NODE === 10 doctype type console.log( document.doctype.nodeName, //logs 'html' also try document.doctype to get <!DOCTYPE html> document.doctype.nodeType //logs 10 which maps to DOCUMENT_TYPE_NODE ); //This is DOCUMENT_NODE or nodeType 9 because Node.DOCUMENT_NODE === 9 document Node type console.log( document.nodeName, //logs '#document' document.nodeType //logs 9 which maps to DOCUMENT_NODE ); //This is DOCUMENT_FRAGMENT_NODE or nodeType 11 because Node.DOCUMENT_FRAGMENT_NODE === 11 Document Fragmentation Node console.log( document.createDocumentFragment().nodeName, //logs '#document-fragment' document.createDocumentFragment().nodeType //logs 11 which maps to DOCUMENT_FRAGMENT_NODE ); //This is ELEMENT_NODE or nodeType 1 because Node. ELEMENT_NODE === 1 Element Node Type console.log( document.querySelector('a').nodeName, //logs 'A' document.querySelector('a').nodeType //logs 1 which maps to ELEMENT_NODE ); //This is TEXT_NODE or nodeType 3 because Node.TEXT_NODE === 3 Document Node Type console.log( document.querySelector('a').firstChild.nodeName, //logs '#text' document.querySelector('a').firstChild.nodeType //logs 3 which maps to TEXT_NODE ); </script> </body> </html>
We can directly determine whether a node belongs to a certain type of node by nodeType: (This can see the top table)
<!DOCTYPE html> <html lang="en"> <body> <a href="#">Hi</a> <script> //is <a> a ELEMENT_NODE? console.log(document.querySelector('a').nodeType === 1); //logs true, <a> is an Element node //or use Node.ELEMENT_NODE which is a property containg the numerice value of 1 console.log(document.querySelector('a').nodeType === Node.ELEMENT_NODE); //logs true, <a> is an Element node </script> </body> </html>
Note: The return value of nodeNam may change in the DOM 4 specification
1.6 Get nodeValue
NoeValue only exists for text and comment types
<!DOCTYPE html> <html lang="en"> <body> <a href="#">Hi</a> <script> //logs null for DOCUMENT_TYPE_NODE, DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE, ELEMENT_NODE below console.log(document.doctype.nodeValue); console.log(document.nodeValue); console.log(document.createDocumentFragment().nodeValue); console.log(document.querySelector('a').nodeVale); //logs string of text console.log(document.querySelector('a').firstChild.nodeValue); //logs 'Hi' </script> </body> </html>
1.7 Create element and text nodes
<!DOCTYPE html> <html lang="en"> <body> <script> var elementNode = document.createElement('div'); console.log(elementNode, elementNode.nodeType); //log <div> 1, and 1 indicates an element node var textNode = document.createTextNode('Hi'); console.log(textNode, textNode.nodeType); //logs Text {} 3, and 3 indicates a text node </script> </body> </html>
Note: createElement accepts only one parameter, and this string returns the same string as the tagName of this node object
<script> var a = document.createElement("mmm"); console.log(a.tagName); </script>
Note: The createAttribute method has been abandoned and getAttribute,setAttribute,removeAttribute, createComment are now recommended for creating annotations
1.8 Create and add element and text nodes
We use innerHTML,outerHTML,textContent, and insertAdjacentHTML attributes and methods to quickly create node fragments
<!DOCTYPE html> <html lang="en"> <body> <div id="A"></div> <span id="B"></span> <div id="C"></div> <div id="D"></div> <div id="E"></div> <script> //create a strong element and text node and add it to the DOM document.getElementById('A').innerHTML = '<strong>Hi</strong>'; //create a div element and text node to replace <span id="B"></div> (notice span#B is replaced) document.getElementById('B').outerHTML = '<div id="B" class="new">Whats Shaking</div>' //create a text node and update the div#C with the text node document.getElementById('C').textContent = 'dude'; //NON standard extensions below i.e. innerText & outerText //create a text node and update the div#D with the text node document.getElementById('D').innerText = 'Keep it'; //create a text node and replace the div#E with the text node (notice div#E is gone) document.getElementById('E').outerText = 'real!'; console.log(document.body.innerHTML); /* logs <div id="A"><strong>Hi</strong></div> <div id="B" class="new">Whats Shaking</div> <span id="C">dude</span> <div id="D">Keep it</div> real! */ </script> </body> </html>
The insertAdjacentHTML method is the best way to quickly add elements before and after the beginning.
<!DOCTYPE html> <html lang="en"> <body><i id="elm">how</i> <script> var elm = document.getElementById('elm'); elm.insertAdjacentHTML('beforebegin', '<span>Hey-</span>'); elm.insertAdjacentHTML('afterbegin', '<span>dude-</span>'); elm.insertAdjacentHTML('beforeend', '<span>-are</span>'); elm.insertAdjacentHTML('afterend', '<span>-you?</span>'); console.log(document.body.innerHTML); /* logs <span>Hey-</span><i id="A"><span>dude-</span>how<span>-are</span></i><span>-you?</span> */ </script> </body> </html>
Note: insertAdjacentHTML beforebegin and afterend can only work if a node has a parent node
outerhtml is incompatible before firefox 11, use polyfill
textContent can get the contents of script tags, innerText cannot
innerText returns the value of the hidden element, while textContent does not
1.10 Extract a portion of node fragments
<!DOCTYPE html> <html lang="en"> <body> <div id="A"><i>Hi</i></div> <div id="B">Dude<strong> !</strong></div> <script> console.log(document.getElementById('A').innerHTML); //logs '<i>Hi</i>' console.log(document.getElementById('A').outerHTML); //logs <div id="A">Hi</div> //notice that all text is returned even if its in child element nodes (i.e. <strong> !</strong>) console.log(document.getElementById('B').textContent); //logs 'Dude !' //NON standard extensions below i.e. innerText & outerText console.log(document.getElementById('B').innerText); //logs 'Dude !' console.log(document.getElementById('B').outerText); //logs 'Dude !' </script> </body> </html>
1.10 appendChild and insertBefore
<!DOCTYPE html> <html lang="en"> <body> <p>Hi</p> <script> //create a blink element node and text node var elementNode = document.createElement('strong'); var textNode = document.createTextNode(' Dude'); //append these nodes to the DOM document.querySelector('p').appendChild(elementNode); document.querySelector('strong').appendChild(textNode); //log's <p>Hi<strong> Dude</strong></p> console.log(document.body.innerHTML); </script> </body> </html>
<!DOCTYPE html> <html lang="en"> <body> <ul> <li>2</li> <li>3</li> </ul> <script> //create a text node and li element node and append the text to the li var text1 = document.createTextNode('1'); var li = document.createElement('li'); li.appendChild(text1); //select the ul in the document var ul = document.querySelector('ul'); /* add the li element we created above to the DOM, notice I call on <ul> and pass reference to <li>2</li> using ul.firstChild */ ul.insertBefore(li,ul.firstChild); console.log(document.body.innerHTML); /*logs <ul> <li>1</li> <li>2</li> <li>3</li> </ul> */ </script> </body> </html>
Note: insertBefore has the same effect as appendChild if it does not pass two parameters.
1.11 removeChild and replaceChild
Removing a node takes two steps. First you need to determine which node you want to move, and then you need to remove it with its parentNode.
<!DOCTYPE html> <html lang="en"> <body> <div id="A">Hi</div> <div id="B">Dude</div> <script> //remove element node var divA = document.getElementById('A'); divA.parentNode.removeChild(divA); //remove text node var divB = document.getElementById('B').firstChild; divB.parentNode.removeChild(divB); //log the new DOM updates, which should only show the remaining empty div#B console.log(document.body.innerHTML); </script> </body> </html>
<!DOCTYPE html> <html lang="en"> <body> <div id="A">Hi</div> <div id="B">Dude</div> <script> //replace element node var divA = document.getElementById('A'); var newSpan = document.createElement('span'); newSpan.textContent = 'Howdy'; divA.parentNode.replaceChild(newSpan,divA); //replace text node var divB = document.getElementById('B').firstChild; var newText = document.createTextNode('buddy'); divB.parentNode.replaceChild(newText, divB); //log the new DOM updates, console.log(document.body.innerHTML); </script> </body> </html>
Note: It is recommended that innerHTML,outerHTML be used instead of replaceChild,removeChild, replaceChild, and removeChild, whose return values are the node objects you remove and are stored in memory for you to use.DOM 4 includes remove() and replace()
1.12 cloneNode
<!DOCTYPE html> <html lang="en"> <body> <ul> <li>Hi</li> <li>there</li> </ul> <script> var cloneUL = document.querySelector('ul').cloneNode(); console.log(cloneUL.constructor); //logs HTMLUListElement() console.log(cloneUL.innerHTML); //logs (an empty string) as only the ul was cloned </script> </body> </html>
Note: When cloning an element node, all attributes and values will be cloned. In fact, attributes are only copies, and all event handles that can be added to DOM nodes, such as event handles, will be lost.
The risk of cloneNode is that it can cause multiple nodes with the same id in the DOM tree.
1.13 NodeList and HTMLCollection
1.14 traversal
<!DOCTYPE html> <html lang="en"> <body> <ul> <li>Hi</li> <li>there</li> </ul> <script> var ulElementChildNodes = document.querySelector('ul').childNodes; console.log(ulElementChildNodes); //logs an array like list of all nodes inside of the ul /*Call forEach as if its a method of NodeLists so we can loop over the NodeList. Done because NodeLists are array like, but do not directly inherit from Array*/ Array.prototype.forEach.call(ulElementChildNodes,function(item){ console.log(item); //logs each item in the array }); </script> </body> </html>
Note: childNodes not only returns element nodes, but also other types of nodes
In ES6, we can use Array.from.
- parentNode
- firstChild
- lastChild
- nextSibling
- previousSibling
- firstElementChild
- lastElementChild
- nextElementChild
- previousElementChild
- children
Note: Although not mentioned, this method can be used to obtain the number of element nodes
1.17 Use contains and compareDocumentPosition to determine the location of nodes
<!DOCTYPE html> <html lang="en"> <body> <script> // is <body> inside <html lang="en"> ? var inside = document.querySelector('html').contains(document.querySelector('body')); console.log(inside); //logs true </script> </body> </html>
1.18 Determine two node relationships
<!DOCTYPE html> <html lang="en"> <body> <input type="text"> <input type="text"> <textarea>foo</textarea> <textarea>bar</textarea> <script> //logs true, because they are exactly idential var input = document.querySelectorAll('input'); console.log(input[0].isEqualNode(input[1])); //logs false, because the child text node is not the same var textarea = document.querySelectorAll('textarea'); console.log(textarea[0].isEqualNode(textarea[1])); </script> </body> </html>
compareDocumentPosition
1.18 Compare whether two element nodes are equal
<!DOCTYPE html> <html lang="en"> <body> <input type="text"> <input type="text"> <textarea>foo</textarea> <textarea>bar</textarea> <script> //logs true, because they are exactly idential var input = document.querySelectorAll('input'); console.log(input[0].isEqualNode(input[1])); //logs false, because the child text node is not the same var textarea = document.querySelectorAll('textarea'); console.log(textarea[0].isEqualNode(textarea[1])); </script> </body> </html>
Note: If you don't mind if the two nodes are equal and you just want to make sure they refer to the same pointer, you can use == instead
(End of Chapter 1)
Reprinted at: https://my.oschina.net/u/1792175/blog/598104