JavaScript DOM节点类型
关系类的选择也有一个语法叫"节点树",把关系拟成了一个数形结构
什么意思呢?
html代码有一个树型结构,最顶层的是div有两个孩子span、div,这个孩子div还有一个孩子是p标签
<div> <span></span> <div> <p></p> </div> </div>
接下来的方法就是基于这个树形结构所有的关系,把元素全部选出来
比如,树形结构有父子关系、还有兄弟关系,左边兄弟关系、右边兄弟关系,
下面是遍历这个节点树的方法,总称叫"遍历节点树"
一、遍历节点树
parentNode -> 父节点(最顶端的parentNode为#document);
childNodes -> 子节点们
firstChild -> 第一个子节点
lastChild -> 最后一个子节点
nextSibling -> 后一个兄弟节点
previousSibling -> 前一个兄弟节点
一个元素在DOM操作里叫一个"节点"(新词),元素节点、元素、标签指的都是一个人,学了DOM之后名字就更加高大上了,以后管标签叫DOM元素,意思是可被DOM操作的元素就叫"DOM元素"。
1、parentNode
选中下面的strong元素
<div> <strong></strong> <span></span> <em></em> </div> <script> var oStrong = document.getElementsByTagName('strong')[0]; // 选中strong元素 </script>
选中strong元素之后,这个oStrong代表一个DOM元素,就是一个HTML元素选出来一个DOM的形式
这个DOM形式的元素(DOM对象),身上有很多属性和方法,其中有一个属性就是parentNode(一切DOM元素都有这些属性和方法)
oStrong.parentNode存着的是string的父元素,它的父元素是div,通过这种方法找到div元素
<div> <strong></strong> <span></span> <em></em> </div> <script> var oStrong = document.getElementsByTagName('strong')[0]; console.log(oStrong.parentNode); // <div>...</div> </script>
Strong.parentNode代表了div元素,这个div也是DOM元素,它身上也有个爹,它爹也是parentNode
<div> <strong></strong> <span></span> <em></em> </div> <script> var Strong = document.getElementsByTagName('strong')[0]; console.log(Strong.parentNode); // string元素的父元素的div console.log(Strong.parentNode.parentNode); // div的父元素是body console.log(Strong.parentNode.parentNode.parentNode); // body的父元素是HTML console.log(Strong.parentNode.parentNode.parentNode.parentNode); // HTML的父元素是#document。 console.log(Strong.parentNode.parentNode.parentNode.parentNode.parentNode); // document是顶层了,没有了返回null </script>
parentNode最顶层的父级节点是#document
#document是HTML它爹,#document包含HTML,包含的关系就可以认为是一种父子关系
2、childNodes
一个元素找它的parendNode只能找到一个元素,而找它的chileNodes孩子节点就不一定有多少个了
把下面div选出来,遍历节点树找chileNodes
<div> <strong> <span>1</span> </strong> <span></span> <em></em> </div> <script> var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.childNodes); // NodeList(7) [text, strong, text, span, text, em, text] console.log(oDiv.childNodes.length); // 7 </script>
oDiv.chileNodes找出来肯定是"类数组"
这个类数组有多少个节点?
直系的子元素就3个,然而它的长度是7
为什么长度是7呢?
这里是遍历节点树没说只有HTML节点算节点,childNodes选的是div下面的所有子节点,节点的类型是五花八门的
节点的类型:
元素节点 —— 1
属性节点 —— 2
文本节点 —— 3
注释节点 —— 8
document —— 9
DocumentFragment —— 11
首先看元素节点、文本节点、注释节点,把这三个先记住就行了
现在div下的childNodes选择的是它的子节点们,div的子节点们(oDiv.childNodes)一共有多少个?
<div> <!-- This is comment --> <strong></strong> <span></span> </div> <script> var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.childNodes); // NodeList(7) [text, comment, text, strong, text, span, text] </script>
第一个是文本节点,第二个是注释节点,第三个是文本节点,第四个是元素节点,第五个是文本节点,第六个是元素节点,第七个是文本节点
有7个子节点,div的子节点们是并列结构的,并且有很多个
再加一个文本123,现在div有多少个节点?
<div> 123 <!-- This is comment --> <strong></strong> <span></span> </div> <script> var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.childNodes); // NodeList(7) [text, comment, text, strong, text, span, text] </script>
还是7个子节点,一定要分辩出节点的个数,节点是分不同类型的
3、firstChild
firstChild能选择一个元素里面的第一个子节点
<div> 123 <!-- This is comment --> <strong></strong> <span></span> </div> <script> var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.firstChild); // "123" </script>
第一子节点oDiv.firstChild是文本节点"123"。
4、lastChild
oDiv.lastChild是最后一个节点
<div> 123 <!-- This is comment --> <strong></strong> <span></span> </div> <script> var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.lastChild); // #text </script>
最后一个节点还是文本节点,只不过是空没有内容,所以返回的是#text
5、nextSibling
Sibling是兄弟的意思,nextSibling是下一个兄弟节点
<div> 123 <!-- This is comment --> <strong></strong> <span></span> </div> <script> var oStrong = document.getElementsByTagName('strong')[0]; // 选中strong元素。 console.log(oStrong.nextSibling); // strong下一个兄弟节点是文本"#text" console.log(oStrong.nextSibling.nextSibling); // "#text"文本的下一个兄弟节点是"<span></span>" console.log(oStrong.nextSibling.nextSibling.nextSibling); // span的再下一个兄弟节点还是文本"#text" console.log(oStrong.nextSibling.nextSibling.nextSibling.nextSibling); // #text文本再下一个兄弟节点什么也没有了"null" </script>
6、previousSibling
previous是前一个的意思,previousSibling前一个兄弟节点
<div> 123 <!-- This is comment --> <strong></strong> <span></span> </div> <script> var oStrong = document.getElementsByTagName('strong')[0]; console.log(oStrong.nextSibling.previousSibling); // strong下一个的前一个是strong自己 </script>
上面是遍历节点树,下面也是遍历树,
但是遍历的是更加方便的树,叫做遍历元素节点树,这回的节点树基于真正的元素节点树
二、基于元素节点树的遍历
parentElement 返回当前元素的父元素节点(IE9以下不兼容)
children 只返回当前元素的元素子节点
node.childElementCount 当前元素节点的子元素节点个数(IE9以下不兼容)
firstElementChild 返回的是第一个元素节点(IE9以下不兼容)
lastElementChild 返回的是最后一个元素节点(IE9以下不兼容)
nextElementSibling 前一个兄弟元素节点(IE9以下不兼容)
previousElementSibling 返回后一个兄弟元素节点(IE9以下不兼容)
1、parentElement
<div> 123 <!-- This is comment --> <strong></strong> <span></span> </div> <script> var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.parentElement); // div的父元素节点是"body" console.log(oDiv.parentElement.parentElement); // body的父元素节点是"HTML" console.log(oDiv.parentElement.parentElement.parentElement); // html的父元素节点是"null" </script>
HTML元素还有父节点吗?
#document不叫元素它自成一个节点,#document不是元素节点,所以html元素节的父点是null
parentElement和parentNode的区别就是parentElement不能到#document。
2、children
div元素子节点只有两个
<div> 123 <!-- This is comment --> <strong></strong> <span></span> </div> <script> var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.children); // HTMLCollection(2) [strong, span] console.log(oDiv.children[0]); // <strong></strong> console.log(oDiv.children[1]); // <span></span> console.log(oDiv.children[2]); // 没有节点返回undefined </script>
children和childNodes不一样,children是元素子节点
3、childElementCount
childElementCount是一个非常没用的属性
<div> 123 <!-- This is comment --> <strong></strong> <span></span> </div> <script> var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.childElementCount); // 返回结果是2,求的是div元素子元素节点的个数,建议不要记了 </script>
node.childElementCount直接等于node.children.length
<div> 123 <!-- This is comment --> <strong></strong> <span></span> </div> <script> var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.childElementCount); // 2 console.log(oDiv.children.length); // 2 console.log(oDiv.childElementCount + ' == ' + oDiv.children.length); // 2 == 2 </script>
node.childElementCount求当前元素节点的子元素子节点个数,建议直接用node.children.length
4、firstElementChild
div的第一个元素子节点 div.firstElementChild
5、lastElementChild
div的最后一个元素子节点 div.lastElementChild
<div> 123 <!-- This is comment --> <strong></strong> <span></span> </div> <script> var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.firstElementChild); // <strong></strong> console.log(oDiv.lastElementChild); // <span></span> </script>
6、nextElementSibling
nextElementSibling下一个兄弟元素节点
7、previousElementSibling
previousElementSibling前一个兄弟元素节点
<div> 123 <!-- This is comment --> <strong></strong> <span></span> <em></em> <i></i> <b></b> </div> <script> var Strong = document.getElementsByTagName('strong')[0]; // 选出strong元素 console.log(Strong.nextElementSibling); // strong下一个元素节点是<span></span> console.log(Strong.nextElementSibling.nextElementSibling); // span下一个元素节点是<em></em> console.log(Strong.nextElementSibling.nextElementSibling.previousElementSibling);// em的上一个元素节点回到<span></span> console.log(Strong.nextElementSibling.nextElementSibling.previousElementSibling.previousElementSibling); // 再previousElementSibling又回到strong了 console.log(Strong.nextElementSibling.nextElementSibling.previousElementSibling.previousElementSibling.previousElementSibling); // 再previousElementSibling变成null </script>
遍历节点树不区分元素不元素的,这些方法任何一个浏览器都好使,
但是基于元素节点树的这些方法除了children以外,都是IE9及IE9以下不兼容的,真正在开发的时候用的最多的是children方法
三、节点的四个属性
nodeName
元素的标签名,以大写形式表示,只读
nodeValue
Text节点或Comment节点的文本内容,可读写
nodeType
该节点的类型,只读
attributes
Element节点的属性集合
节点的一个方法 Node.hasChildNodes();
每一个节点基本上都有四个属性,什么是每一个节点?元素节点、文本节点……那些节点都有这四个属性。
1、nodeName
第一个属性是nodeName,比如document是个节点也有nodeName属性,打印出来是#document形式
console.log(document.nodeName); // #document
div的子节点们,第一个兄弟子节点是123文本,文本的nodeName属性是#text
<div> 123 <!-- This is comment --> <strong></strong> <span></span> <em></em> <i></i> <b></b> </div> <script> var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.firstChild.nodeName); // #text </script>
第二个兄弟子节点是注释,注释节点的nodeName属性是#comment
<div> 123 <!-- This is comment --> <strong></strong> <span></span> <em></em> <i></i> <b></b> </div> <script> var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.childNodes[1]); // <!-- This is comment --> console.log(oDiv.childNodes[1].nodeName); // 注释接的nodeName属性是"#comment" </script>
元素子节点是第[3]个,元素节点的nodeName属性是STRONG
<div> 123 <!-- This is comment --> <strong></strong> <span></span> <em></em> <i></i> <b></b> </div> <script> var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.childNodes[3].nodeName); // STRONG </script>
nodeName属性能区别出标签是什么名,返回的是一个字符串,只读取值不能写入,nodeName属性不能赋值
<div> 123 <!-- This is comment --> <strong></strong> <span></span> <em></em> <i></i> <b></b> </div> <script> var oDiv = document.getElementsByTagName('div')[0]; oDiv.childNodes[3].nodeName = 'abc'; // 把STRONG改成abc console.log(oDiv.childNodes[3].nodeName); // 再访问返回还是"STRONG" </script>
2、nodeValue
nodeValue属性不是所以的节点都有,只有文本节点(Text)和注释节点(Comment)有
div.childNodes[0] 第0位是文本节点,文本节点的nodeValue是123
<div> 123 <!-- This is comment --> <strong></strong> <span></span> <em></em> <i></i> <b></b> </div> <script> var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.childNodes[0].nodeValue); // 123 </script>
div.childNodes[0]取值是123,然后赋值234
<div> 123 <!-- This is comment --> <strong></strong> <span></span> <em></em> <i></i> <b></b> </div> <script> var oDiv = document.getElementsByTagName('div')[0]; oDiv.childNodes[0].nodeValue = 234; // 赋值234 console.log(oDiv.childNodes[0].nodeValue); // 再取值就变成234了 console.log(oDiv.childNodes[0]); // 这样取也是"234",只不过这样取得是节点的值 // 页面上显示也变成234了 </script>
oDiv.childNodes[0].nodeValue 这样取的是内容,返回的是数字类型
oDiv.childNodes[0] 这样取的是节点,返回的是字符串类型
div.childNodes[1]第一位是注释,注释也有nodeValue属性,注释节点的nodeValue属性也是可以写入读取的
<div> 123 <!-- This is comment --> <strong></strong> <span></span> <em></em> <i></i> <b></b> </div> <script> var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.childNodes[1]); // <!-- This is comment --> </script>
修改注释节点的nodeValue属性
<div> 123 <!-- This is comment --> <strong></strong> <span></span> <em></em> <i></i> <b></b> </div> <script> var oDiv = document.getElementsByTagName('div')[0]; oDiv.childNodes[1].nodeValue = "That is comment"; // "This is comment"改成"That is comment" console.log(oDiv.childNodes[1]); // 再看注释节点变成"<!-- That is comment -->" </script>
nodeValue属性只有"文本节点"和"注释节点"上面有,其它节点都没有,比如document节点返回null
console.log(document.nodeValue); // null
3、nodeType
终于到重点了,这四个属性里面最有用的就是nodeType
nodeType是干嘛的?
它能帮我们分辨一个节点到底是什么节点,比如现在给我们一个节点,不知道是什么节点,分辩出这是什么节点,只能通过nodeType属性
每一个节点都有nodeType属性,nodeType属性里面装的是节点的类型。
节点类型:
元素节点 —— 1
属性节点 —— 2
文本节点 —— 3
注释节点 —— 8
document —— 9
DocumentFragment —— 11(文档碎片节点)
节点后面跟的数字是干什么的呢?
调用该节点的nodeType返回的就是这些对应的数
用document节点试一下,document.nodeType返回9
console.log(document.nodeType); // 9
div第一个子节点是注释节点,注释节点的nodeType返回8
<div> 123 <!-- This is comment --> <strong></strong> <span></span> <em></em> <i></i> <b></b> </div> <script> var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.childNodes[1].nodeType); // 注释节点的nodeType返回 8 console.log(oDiv.childNodes[0].nodeType); // 文本节点的nodeType返回 3 console.log(oDiv.childNodes[3].nodeType); // 元素节点的nodeType返回 1 </script>
小例子
封装一个retElementChild(node)方法,函数里面放一个参数node,传一个DOM节点(node)进来把node里面所有的直接子元素节点放到一个数组里面返回,并且不允许用children
思路,不允许用children,把node.childNodes遍历一遍,把nodeType等于1的元素,放到一个数组里返回
<div> 123 <!-- This is comment --> <strong></strong> <span></span> <em></em> <i></i> <b></b> </div> <script> function retElementChild(node){ var arr = [], child = node.childNodes, len = child.length; for(var i = 0; i < len; i++){ if(child[i].nodeType === 1){ arr.push(child[i]) } } return arr; } var div = document.getElementsByTagName('div')[0]; console.log(retElementChild(div));// (5) [strong, span, em, i, b] </script>
方法再做的丰满些,返回更像系统的类数组
<div> 123 <!-- This is comment --> <strong></strong> <span></span> <em></em> <i></i> <b></b> </div> <script> function retElementChild(node){ var temp = { // 不用[]用类数组temp length : 0, push : Array.prototype.push }, child = node.childNodes, len = child.length; for(var i = 0; i < len; i++){ if(child[i].nodeType === 1){ temp.push(child[i]); } } return temp; } var div = document.getElementsByTagName('div')[0]; console.log(retElementChild(div)); // {0: strong, 1: span, 2: em, 3: i, 4: b, length: 5, push: ƒ} </script>
怎么让它更形象点,让它看起来就是一个数组?
加splice方法,有了splice : Array.prototype.splice看起来更像数组,在控制台上看长的跟更像数组了
<div> 123 <!-- This is comment --> <strong></strong> <span></span> <em></em> <i></i> <b></b> </div> <script> function retElementChild(node){ var temp = { length : 0, push : Array.prototype.push, splice : Array.prototype.splice // 有了"splice"看起来更像数组 }, child = node.childNodes, len = child.length; for(var i = 0; i < len; i++){ if(child[i].nodeType === 1){ temp.push(child[i]); } } return temp; } var div = document.getElementsByTagName('div')[0]; console.log(retElementChild(div)); // Object(5) [strong, span, em, i, b, push: ƒ, splice: ƒ] </script>
4、attributes
特殊的 属性节点 —— 2 属性节点基本上是没什么用但是它存在
div元素上面有两个属性节点,
class="demo" 在js里面认为是一个属性节点,节点类型是2,
id="only" 也是一个属性节点,怎么再js里面查看属性节点呢?
<div id="only" class="demo"></div>
div有两个属性,oDiv.attributes就是这两个属性节点的集合,两个属性放到一个类数组里面去,第0位就是一个属性节点
<div id="only" class="demo"></div> <script> var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.attributes); // amedNodeMap {0: id, 1: class, id: id, class: class, length: 2} console.log(oDiv.attributes[0]); // id="only" console.log(oDiv.attributes[0].nodeType); // 这个属性节点的nodeType返回的类型是 2 </script>
属节点的方法,可以把属性节点的 值 和 名 取出来
<div id="only" class="demo"></div> <script> var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.attributes[0].value); // only console.log(oDiv.attributes[0].name); // id </script>
属性节点能赋值吗?
<div id="only" class="demo"></div> <script> var oDiv = document.getElementsByTagName('div')[0]; oDiv.attributes[0].value = 'abc'; console.log(oDiv); // 能赋值,id的值变了<div id="abc" class="demo"></div> </script>
属性名id能改吗?
<div id="only" class="demo"></div> <script> var oDiv = document.getElementsByTagName('div')[0]; oDiv.attributes[0].name = 'abc'; console.log(oDiv); // <div id="abc" class="demo"></div>属性名不能改 </script>
属性值能改,属性名改不了,
但是我们不这么用,因为属性值可以通过getAttribute方法和setAttribute方法操作属性值
5、Node.hasChildNodes()方法
每个节点都有一个Node.hasChildNodes()方法,hasChildNodes翻译的意思是有没有子节点,返回结果不是true就是false
下面div有子节点吗?
<div id="only" class="demo"> <span></span> </div> <script> var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.hasChildNodes()); // 有子节点返回true </script>
div里面就有注释,还有子节点吗?
还有节点返回true,说的节点没说元素节点,没区别节点类型还是有子节点
<div id="only" class="demo"> <!-- this is comment --> </div> <script> var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.hasChildNodes()); // true </script>
div里面都删除了,还有子节点吗?
还有子节点返回true,div里面不是空,虽然什么也没写也算文本,div里面是文字分隔符文本
<div id="only" class="demo"> </div> <script> var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.hasChildNodes()); // true </script>
把这些都删了,现在还有子节点吗?
<div id="only" class="demo"></div> <script> var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.hasChildNodes()); // false </script>
这种情况下才返回false,但凡里面有个空格、回车返回的都不可能是false