JavaScript DOM节点类型
关系类的选择也有一个语法叫“节点树”,把关系拟成了一个数形结构
什么意思呢?
html 代码有一个树型结构,
最顶层的是 div 有两个孩子 span、div,这个孩子 div 还有一个孩子是 p 标签
<div> <span></span> <div> <p></p> </div> </div>
接下来的方法就是基于这个树形结构所有的关系,把元素全部选出来
比如,
树形结构有父子关系、还有兄弟关系,左边兄弟关系、右边兄弟关系,
下面是遍历这个节点树的方法,总称叫遍历节点树
一、遍历节点树
1. parentNode 父节点(最顶端的 parentNode 为 #document)
2. childNodes 子节点们
3. firstChild 第一个子节点
4. lastChild 最后一个子节点
5. nextSibling 后一个兄弟节点
6. 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 代表一个 din 元素,就是一个 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
它能选择一个元素里面的第一个子节点
<div> 123 <!-- This is comment --> <strong></strong> <span></span> </div> <script> var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.firstChild); // "123" </script>
第一子节点是文本节点"123"
4. lastChild
oDiv.lastChild 是最后一个节点,
最后一个节点还是文本节点,只不过是空没有内容,所以返回的是 #text
<div> 123 <!-- This is comment --> <strong></strong> <span></span> </div> <script> var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.lastChild); // #text </script>
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>
上面是遍历节点树,下面也是遍历树,
但是遍历的是更加方便的树,叫做遍历元素节点树,这回的节点树基于真正的元素节点树
二、基于元素节点树的遍历
1. parentElement 返回当前元素的父元素节点(IE9以下不兼容)
2. children 只返回当前元素的元素子节点
3. node.childElementCount 当前元素节点的子元素节点个数(IE9以下不兼容)
4. firstElementChild 返回的是第一个元素节点(IE9以下不兼容)
5. lastElementChild 返回的是最后一个元素节点(IE9以下不兼容)
6. nextElementSibling 前一个兄弟元素节点(IE9以下不兼容)
7. 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
它是一个非常没用的属性
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,求的是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>
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
下一个兄弟元素节点
7. 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
比如 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
var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.childNodes[1]); // <!-- This is comment --> console.log(oDiv.childNodes[1].nodeName); // 注释接的nodeName属性是"#comment"
第 [3] 个是元素子节点,
元素节点的 nodeName 属性是 STRONG
var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.childNodes[3].nodeName); // STRONG
nodeName 属性能区别出标签是什么名,返回的是一个字符串,只读取值不能写入,该属性不能赋值
var oDiv = document.getElementsByTagName('div')[0]; oDiv.childNodes[3].nodeName = 'abc'; // 把STRONG改成abc console.log(oDiv.childNodes[3].nodeName); // 再访问返回还是STRONG
2. 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,页面上显示也变成 234 了
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",只不过这样取得是节点的值
oDiv.childNodes[0].nodeValue 这样取的是内容,返回的是数字类型
oDiv.childNodes[0] 这样取的是节点,返回的是字符串类型
div.childNodes[1] 第一位是注释,注释也有 nodeValue 属性,注释节点的 nodeValue 属性也是可以写入读取的
var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.childNodes[1]); // <!-- This is comment -->
修改注释节点的 nodeValue 属性
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 -->
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); // 注释节点的返回 8 console.log(oDiv.childNodes[0].nodeType); // 文本节点的返回 3 console.log(oDiv.childNodes[3].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>
方法再做的丰满些,返回更像系统的类数组
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: ƒ}
怎么让它更形象点,让它看起来就是一个数组?
加 splice 方法,有了 splice : Array.prototype.splice 看起来更像数组,在控制台上看长的跟更像数组了
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: ƒ]
4. attributes
特殊的 属性节点 —— 2 属性节点基本上是没什么用但是它存在
div 元素上面有两个属性节点,
1. class="demo" 在 js 里面认为是一个属性节点,节点类型是 2,
2. id="only" 也是一个属性节点,
<div id="only" class="demo"></div>
怎么在 js 里面查看属性节点呢?
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>
属性节点的方法可以把属性节点的 值 和 名 取出来
var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.attributes[0].value); // only console.log(oDiv.attributes[0].name); // id
属性节点能赋值吗?
var oDiv = document.getElementsByTagName('div')[0]; oDiv.attributes[0].value = 'abc'; console.log(oDiv); // 能赋值,id的值变了<div id="abc" class="demo"></div>
属性名 id 能改吗?
var oDiv = document.getElementsByTagName('div')[0]; oDiv.attributes[0].name = 'abc'; console.log(oDiv); // 属性名不能改<div id="abc" class="demo"></div>
属性值能改,属性名改不了,
但是我们不这么用,因为属性值可以通过 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>
把这些都删了,现在还有子节点吗?
这种情况下才返回 false,但凡里面有个空格、回车返回的都不可能是 false
<div id="only" class="demo"></div> <script> var oDiv = document.getElementsByTagName('div')[0]; console.log(oDiv.hasChildNodes()); // false </script>