开场白:
到底啥叫DOM呢?为啥DOM会被这么多人赋予javascript核心知识点的至高荣誉呢?DOM到底难么?小卖部安全套为何屡遭黑手?女生宿舍内裤为何频频失窃?连环强奸母 猪案,究竟是何人所为?老尼姑。。。咳咳,串了,现在来看看DOM到底是什么鬼!
答:“DOM不是鬼!!!他是javascript的 Document Object Model(文档对象模型)” BUT!!我们更习惯的是称其为“DOM节点”
开始——最常规的认识:
首先,简单介绍一件事情:
javascript是基于对象的一门语言,换句话就是【javascript里一切都是对象】,对象又是什么?有自己的属性和方法的“东西”叫做对象
例如:a.attr , a.fn(),这里的a就是对象,attr就是a的属性,fn就是a的方法。
然后,开始dom的学习。
来两个烂大街的【方法】:
document.getElementById(),
document.getElementsByTagName(),
都知道怎么用,就像这么用:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>到底啥叫DOM?!</title> 6 <script type="text/javascript"> 7 window.onload=function(){ 8 var oDiv=document.getElementById("box"); 9 var aP=document.getElementsByTagName("p"); 10 console.log(oDiv); 11 console.log(aP); 12 } 13 </script> 14 </head> 15 <body> 16 <div id="box"> 17 <p class="node1"></p> 18 <p class="node2"></p> 19 <p class="node3"></p> 20 <p class="node4"></p> 21 <p class="node5"></p> 22 </div> 23 </body> 24 </html>
运行一下代码,会看到控制台为我们解释了这俩玩意儿是用来嘎哈的:
对照控制台的输出可以得出结论:
document.getElementById()是用来获取单个HTML dom节点的(单对象)【包含所有内部节点】,参数是HTML元素的ID。
document.getElementsByTagName()是用来获取HTML dom节点集合的(数组)【包括所有集合项目的内部节点】,参数是HTML元素的标签名。
当然此处会出现【注意】:
仔细观察document.getElementById()和document.getElementsByTagName()这两个表达式,参照前面说的【对象】的low比概念,会发觉,“.”前面的document就像是例子里面的“a”,那么就是说此处我们是在document上面调用getElementById()和getElementsByTagName()这两个方法,在然后前面也说了DOM这玩意儿乳名叫节点,官方叫“Document Object Model(文档对象模型)”,所以也就代表作为对象的DOM,也是可以调用getElementById()和getElementsByTagName()这两个方法的,那么就有:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>到底啥叫DOM?!</title> 6 <script type="text/javascript"> 7 window.onload=function(){ 8 var oDiv=document.getElementById("box"); 9 var aP=oDiv.getElementsByTagName("p"); 10 console.log(oDiv); 11 console.log(aP); 12 } 13 </script> 14 </head> 15 <body> 16 <div id="box"> 17 <p class="node1"></p> 18 <p class="node2"></p> 19 <p class="node3"></p> 20 <p class="node4"></p> 21 <p class="node5"></p> 22 </div> 23 </body> 24 </html>
结果和上面一样,不再赘述。
接着——认识DOM的类型:
现在已经能顺利的收集到DOM了,可是由于DOM还分好些类型,所以在处理过程中难免会遇到一些区别对待的问题,而在区别对待之前,必须先知道DOM的类型【nodeType】
【nodeType】是个好东西!它能带我们认识,谁是谁,如下代码:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>到底啥叫DOM?!</title> 6 <script type="text/javascript"> 7 window.onload=function(){ 8 var aP=document.getElementsByTagName("p"); 9 for(var i=0;i<aP.length;i++){ 10 console.log(aP[i].nodeType); 11 } 12 } 13 </script> 14 </head> 15 <body> 16 <div id="box"> 17 <p class="node1"></p> 18 <p class="node2"></p> 19 <p class="node3"></p> 20 <p class="node4"></p> 21 <p class="node5"></p> 22 </div> 23 </body> 24 </html>
运行代码,可以看到,chrome给出的答案是:1
这代表什么?
dom元素常见类型如下:
1:元素节点,只要是在document下的html元素都称为节点树,什么body、h1、p、ul、li。。。。等等等等
2:属性节点,简单理解为所有html元素的属性
3:文本节点,即网页中的文本
8:注释节点,即“//”或者“/**/”
9:文档节点,即"document"本身
由此可见,上面输出的意思是:aP获取到的是元素节点的集合。
在试验文本节点和属性节点之前先介绍两个DOM属性:childNodes和children
这俩是用来获取当前节点子节点的属性。
现在使用childNodes来试验一下文本节点和元素节点:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>到底啥叫DOM?!</title> 6 <script type="text/javascript"> 7 window.onload=function(){ 8 var oDiv=document.getElementById("box"); 9 var allNodes=oDiv.childNodes; 10 for(var i=0;i<allNodes.length;i++){ 11 alert(allNodes[i].nodeType); 12 } 13 } 14 </script> 15 </head> 16 <body> 17 <div id="box"> 18 <p class="node1"></p> 19 <p class="node2"></p> 20 <p class="node3"></p> 21 <p class="node4"></p> 22 <p class="node5"></p> 23 </div> 24 </body> 25 </html>
运行结果为:反复弹出3和1.说明了在#box下的所有节点中包含了元素节点和文本节点。
【疑问】元素节点毫无疑问是“p”,可是文本节点呢?
很简单,删除代码中的所有换行和空格,得到如下结果:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>到底啥叫DOM?!</title> 6 <script type="text/javascript"> 7 window.onload=function(){ 8 var oDiv=document.getElementById("box"); 9 var allNodes=oDiv.childNodes; 10 for(var i=0;i<allNodes.length;i++){ 11 alert(allNodes[i].nodeType); 12 } 13 } 14 </script> 15 </head> 16 <body> 17 <div id="box"><p class="node1"></p><p class="node2"></p><p class="node3"></p><p class="node4"></p><p class="node5"></p></div> 18 </body> 19 </html>
现在运行,得到的结果是:弹出所有都是1(元素节点)。那么就能得出结论了,前面的代码弹出的3(文本节点)是代码之间的换行和空格造成的
按理说,使用childNodes就能达到之前说的区别对待dom节点来进行操作了,但遗憾的是,Firefox对childNodes的支持和其他浏览器并不一样,丫不识别其中的文本节点你信么?!那怎么办?可以使用如下的方法:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>到底啥叫DOM?!</title> 6 <script type="text/javascript"> 7 window.onload=function(){ 8 var oDiv=document.getElementById("box"); 9 var allNodes=oDiv.childNodes; 10 for(var i=0;i<allNodes.length;i++){ 11 if(allNodes[i].nodeType==1){ 12 alert("这是元素节点"); 13 }else if(allNodes[i].nodeType==3){ 14 alert("这是文本节点"); 15 } 16 } 17 } 18 </script> 19 </head> 20 <body> 21 <div id="box"> 22 <p class="node1"></p> 23 <p class="node2"></p> 24 <p class="node3"></p> 25 <p class="node4"></p> 26 <p class="node5"></p> 27 </div> 28 </body> 29 </html>
运行代码,可以看到,在chrome里面我们成功的实现了区分不同的节点。
【额外赠送】由于活动期间,现立即订购childNodes还可赠送万能节点识别器——children
children这个属性完美的解决了childNodes的不兼容问题,不过需要注意的是,children并非W3C标准属性,不过由于那高度太高,现在嘛,who care?
试试看:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>到底啥叫DOM?!</title> 6 <script type="text/javascript"> 7 window.onload=function(){ 8 var oDiv=document.getElementById("box"); 9 var allNodes=oDiv.children; 10 for(var i=0;i<allNodes.length;i++){ 11 alert(allNodes[i].nodeType); 12 } 13 } 14 </script> 15 </head> 16 <body> 17 <div id="box"> 18 <p class="node1"></p> 19 <p class="node2"></p> 20 <p class="node3"></p> 21 <p class="node4"></p> 22 <p class="node5"></p> 23 </div> 24 </body> 25 </html>
可以看到弹出的都是1(元素节点),并且这个方法能兼容所有浏览器,在某些情况下还是为我们带来了不少便利。
然后——几个dom的常用方法:
现在,知道了如何获取dom,如何区分dom的类型,要是再知道如何操作dom那就赞了!
这里还有两个比较常用的dom方法:
getAttribute()——获取dom节点的属性值。
setAttribute()——设置dom节点的属性值。
先来看一下getAttribute()的运用:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>到底啥叫DOM?!</title> 6 <script type="text/javascript"> 7 window.onload=function(){ 8 var oDiv=document.getElementById("box"); 9 // 获取oDiv下所有的元素节点 10 var allNodes=oDiv.children; 11 for(var i=0;i<allNodes.length;i++){ 12 // 获取节点的class属性 13 alert(allNodes[i].getAttribute("class")); 14 } 15 } 16 </script> 17 </head> 18 <body> 19 <div id="box"> 20 <p class="node1"></p> 21 <p class="node2"></p> 22 <p class="node3"></p> 23 <p class="node4"></p> 24 <p class="node5"></p> 25 </div> 26 </body> 27 </html>
运行会看到,弹出了所有p标签的class类名。
既然这样没问题,那么使用setAttribute()来设置节点的属性值就没必要演示了。
【试试看】用前面所说的所有方法和属性来写一个选项卡效果(尽可能多使用这些dom属性和方法);
【试试看】用dom知识点写一个全选、反选、取消选择的效果;
既然能获得DOM的属性值(增加属性值)并且对齐作一定的修改,那么是否可以删除呢?答案是肯定的
这个方法就是:removeAttribute
使用方法嘛,其实很傻瓜,一个简单例子好了,没必要作过多的解释:obj.removeAttribute("class"),这就是移除obj对象的class属性。
再然后:局部操作DOM
下面来看两个比较重要的DOM属性:
firstChild和lastChild
当然在解释这两个属性之前还需要在介绍另外两个属性:
firstElementChild和lastElementChild
firstChild属性是指获取DOM子对象组的第一个子对象,其作用和childNodes[0]一样,同样望文生义,lastChild也就是最后一个子对象。那么如何使用这个属性呢?很简单:node代表我们获取到的DOM节点,那么要获得其下属的第一个子节点,只需要【node.firstChild】即可,当然,lastChild也是一样的使用方法,接下来看看一个简单的例子,将这两个属性运用到实际中。
BUT,在写这个例子之前我们还需要了解另外一个属性——nodeValue【节点对象的值】
为什么要顺带提一下呢?因为在我看来这个属性的重要性在目前而言还没有体现出来,因为它仅仅能获取到文本节点的值,对于元素节点,他获取的都是null。不过,可不要小看这个属性,等到高级js编程的时候,在获取节点内容的方法(类似innerHTML)编写上他有着不可替代的作用,这些现在不说。nodeValue的使用方式依然是:node.nodeValue,当然,也可以对修改节点的值:node.nodeValue=text。
好了,现在来看看要做的例子:【转移文本内容】
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Document</title> 6 <style type="text/css"> 7 #text1,#text2{ 8 width: 100px; 9 height: 30px; 10 text-align: center; 11 color:#fff; 12 font-weight: bold; 13 line-height: 30px; 14 } 15 #text1{ 16 background: green; 17 } 18 #text2{ 19 background: blue; 20 } 21 </style> 22 <script type="text/javascript"> 23 window.onload=function(){ 24 // 获取按钮、text1、text2 25 var oSub=document.getElementById("sub"); 26 oP1=document.getElementById("text1"); 27 oP2=document.getElementById("text2"); 28 // 提前将text1的文本节点值(即文本内容)保存在一个变量中 29 moveText=oP1.childNodes[0].nodeValue; 30 // 为按钮绑定点击事件 31 oSub.onclick=function(){ 32 // 设置text1的文本节点为空 33 oP1.childNodes[0].nodeValue=""; 34 // 设置text2的文本节点值为前面保存在变量中的值 35 oP2.childNodes[0].nodeValue=moveText; 36 } 37 } 38 </script> 39 </head> 40 <body> 41 <button id="sub">转移内容</button> 42 <p id="text1">要被转移了</p> 43 <!-- 写一句话的意义在于:p#text2中必须包含有一个文本节点才能对其进行更改节点值的操作 --> 44 <p id="text2">迎接转移</p> 45 </body> 46 </html>
仔细查看上面的例子,两个问题需要思考:
1、为什么要在p#text2中写一句话?(答案在代码中)
引申:那敲一个空格可以么?前文说过空格也是文本节点。
答:不能,前文也说过浏览器对文本节点的解析不同,正解是:IE8.0及其以下版本的浏览器会忽略节点间的空白节点(空格、回车和Tab键),遵循W3C规范的浏览器(Chrome、FireFox、Safari等)则会把这些空白作为文本节点处理。
2、为什么要在最开始就把p#text1的文本节点值保存在一个变量中?
经过上面的例子,现在firstChild和nodeValue的使用方法算是明白了(lastChild没必要再说了)。不过,最开始的时候我们就提出了另外两个属性:firstElementChild和lastElementChild,何解?
先来对比一下这两对属性的名字:
firstChild:第一个子节点 firstElementChild:第一个元素子节点
lastChild:最后一个子节点 lastElementChild:最后一个元素节点
现在能看出区别了吧,他们对应的节点类型不一样。还有一个问题就是IE8及其以下浏览器并不支持firstElementChild和lastElementChild,直接使用firstChild和lastChild即可,具体可以参考上面思考里面的正解。
直接看看这两个节点属性的使用:【控制ul的第一个和最后一个li的背景】
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style> #main{ width: 100px; color: #fff; } #main li{ width: 100%; height: 30px; line-height: 30px; background: #000; margin: 5px auto; } </style> <script type="text/javascript"> window.onload=function(){ // 获取按钮、ul var oMain=document.getElementById("main"), oSub1=document.getElementById("sub1"), oSub2=document.getElementById("sub2"); // 为第一个按钮绑定事件 oSub1.onclick=function(){ // 点击按钮,ul的【第一个元素节点】背景颜色变成蓝色 oMain.firstElementChild.style.background="blue"; } // 为第二额按钮绑定事件 oSub2.onclick=function(){ // 点击按钮,ul的【最后一个元素节点】背景颜色变成红色 oMain.lastElementChild.style.background="red"; } } </script> </head> <body> <button id="sub1">第一个li变色</button> <button id="sub2">最后一个li变色</button> <ul id="main"> <li>节点1</li> <li>节点2</li> <li>节点3</li> <li>节点4</li> <li>节点5</li> </ul> </body> </html>
运行上面的代码,可以看到点击按钮,对应的li会改变背景颜色,不过这真的好了吗?当然不会,上面已经说过,这两个属性有兼容性问题,他们都是W3C标准下的属性,IE8以下的浏览器因为微软的自大,拒绝了准守这个标准,所以,这个程序放到IE8以下浏览器运行是没有作用的。那要怎么做?
再次回顾前面说过的IE8对firstChild和lastChild的支持原理:IE8.0及其以下版本的浏览器会忽略节点间的空白节点(空格、回车和Tab键),遵循W3C规范的浏览器(Chrome、FireFox、Safari等)则会把这些空白作为文本节点处理。
那么我们需要这么修改:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Document</title> 6 <style> 7 #main{ 8 width: 100px; 9 color: #fff; 10 } 11 #main li{ 12 width: 100%; 13 height: 30px; 14 line-height: 30px; 15 background: #000; 16 margin: 5px auto; 17 } 18 </style> 19 <script type="text/javascript"> 20 window.onload=function(){ 21 // 获取按钮、ul 22 var oMain=document.getElementById("main"), 23 oSub1=document.getElementById("sub1"), 24 oSub2=document.getElementById("sub2"); 25 // 为第一个按钮绑定事件 26 oSub1.onclick=function(){ 27 // 点击按钮,ul的【第一个元素节点】背景颜色变成蓝色 28 if(oMain.firstElementChild){ 29 oMain.firstElementChild.style.background="blue"; 30 }else{ 31 oMain.firstChild.style.background="blue"; 32 } 33 34 } 35 // 为第二额按钮绑定事件 36 oSub2.onclick=function(){ 37 // 点击按钮,ul的【最后一个元素节点】背景颜色变成红色 38 if(oMain.lastElementChild){ 39 oMain.lastElementChild.style.background="red"; 40 }else{ 41 oMain.lastChild.style.background="red"; 42 } 43 44 } 45 46 } 47 </script> 48 </head> 49 <body> 50 <button id="sub1">第一个li变色</button> 51 <button id="sub2">最后一个li变色</button> 52 <ul id="main"> 53 <li>节点1</li> 54 <li>节点2</li> 55 <li>节点3</li> 56 <li>节点4</li> 57 <li>节点5</li> 58 </ul> 59 </body> 60 </html>
仔细观察两篇代码的区别,第二篇代码里面有一个判断,当oMain.firstElement存在时,就使用这个属性,若不存在则使用oMain.firstChild。
为什么可以这么判断呢?因为在IE8以下的浏览器里面,oMain.firstElement获取到的是undefined。(建议自己验证一下)
继续深入
有时候我并非一定要寻找首尾的节点,可能是某一个子节点的兄弟节点,这怎么找呢?那么来认识这两个属性:
nextSibling:下一个兄弟节点 nextElementSibling:下一个兄弟节点(W3C标准)
previousSibling:前一个兄弟节点 previousElementSibling:前一个兄弟节点(W3C标准)
对比前面的firstChild和lastChild,这两对属性怎么做也就能知道了,IE8以下的浏览器不支持W3C标准属性,所以需要做兼容。
实践出真知,代码走着:【点击按钮为li排序】——知识点糅合,多体会消化
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Document</title> 6 <style> 7 #main{ 8 width: 100px; 9 color: #fff; 10 } 11 #main li{ 12 width: 100%; 13 height: 30px; 14 line-height: 30px; 15 background: #000; 16 margin: 5px auto; 17 } 18 </style> 19 <script type="text/javascript"> 20 window.onload=function(){ 21 // 获取文本框,按钮,和ul 22 var oUl=document.getElementById("main"), 23 oPre=document.getElementById("sub1"), 24 oNext=document.getElementById("sub2"), 25 oFirst=document.getElementById("sub3"), 26 oLast=document.getElementById("sub4"), 27 oNum=document.getElementById("numText"), 28 oCon=document.getElementById("textCon"); 29 // 为oPre按钮绑定事件 30 oPre.onclick=function(){ 31 // 点击按钮后先获取两个文本框的值input.value; 32 var num=oNum.value, 33 text=oCon.value, 34 // 1、获取索引值对应的子节点——children知识点 35 // 2、然后根据当前子节点获取上一个兄弟节点——previousSibling和previousElementSibling知识点 36 // 3、再获取其兄弟节点的文本节点——nodeValue知识点 37 // 注意:这里的书写形式类似于if。。。else,当oUl.children[num].childNodes[0].previousElementSibling存在时,oChild就取其值,若不存在则取oUl.children[num].childNodes[0].previousSibling的值 38 oChild=oUl.children[num].previousElementSibling.childNodes[0]||oUl.children[num].previousSibling.childNodes[0]; 39 // 为获得的文本节点赋值 40 oChild.nodeValue=text; 41 } 42 // 为oNext绑定事件——操作方式和上面一样 43 oNext.onclick=function(){ 44 var num=oNum.value, 45 text=oCon.value, 46 oChild=oUl.children[num].nextElementSibling.childNodes[0]||oUl.children[num].nextSibling.childNodes[0]; 47 oChild.nodeValue=text; 48 } 49 // 为oFirst绑定事件 50 oFirst.onclick=function(){ 51 var num=oNum.value, 52 text=oCon.value, 53 // 注意此处的调用方式与上面的区别 54 oChild=oUl.firstElementChild.childNodes[0]||oUl.firstChild.childNodes[0]; 55 oChild.nodeValue=text; 56 } 57 // 为oLast绑定事件 58 oLast.onclick=function(){ 59 var num=oNum.value, 60 text=oCon.value, 61 oChild=oUl.lastElementChild.childNodes[0]||oUl.children[num].lastChild.childNodes[0]; 62 oChild.nodeValue=text; 63 } 64 } 65 </script> 66 </head> 67 <body> 68 节点索引:<input type="text" id="numText"> 69 替换内容:<input type="text" id="textCon"> 70 <button id="sub1">替换前一个li</button> 71 <button id="sub2">替换后一个li</button> 72 <button id="sub3">替换第一个li</button> 73 <button id="sub4">替换最后一个li</button> 74 <ul id="main"> 75 <li>节点1</li> 76 <li>节点2</li> 77 <li>节点3</li> 78 <li>节点4</li> 79 <li>节点5</li> 80 </ul> 81 </body> 82 </html>
依然两个问题需要思考:
1、oChild=oUl.children[num].previousElementSibling.childNodes[0]||oUl.children[num].previousSibling.childNodes[0];的书写方式应该如何理解?
2、为什么每一个绑定点击事件都需要在内部var num=oNum.value, text=oCon.value,这两个变量?
当然,答案都在代码里