DOM(Document Object Model)即文档对象模型,针对HTML和XML文档的API(应用程序接口)。DOM描绘了一个层次化的节点树,运行开发人员添加、移除和修改页面的某一部分。DOM中,D(文档)可以理解为整个Web加载的网页文档;O(对象)可以理解为类似window对象之类的东西,可以调用属性和方法,通常也就是document对象;M(模型)可以理解为网页文档的树型结构。
一、节点树
学习过数据结构中树型结构都应该很简单了解,DOM是将HTML的结构理解成一棵节点树,把标签(元素)定义成节点。节点分为三类,
(1)元素节点,也就是标签,
(2)文本节点,标签内的纯文本,
(3)属性节点,标签的属性
二、元素节点
查找元素节点的方法,
方法 | 说明 |
getElementById() | 获取特定ID元素的节点 |
getElementsByTagName() | 获取相同元素的节点列表 |
getElementsByName() | 获取相同名称的节点列表 |
getAttribute() | 获取特定元素节点属性的值,支持自定义属性 |
setAttribute() | 设置特定元素节点属性的值 |
removeAttribute() | 移除特定元素节点属性 |
1、getElementById()方法
getElementById(),参数传递一个元素的id值,这样可以获取到该元素的节点。在实验过程中,出现了无法查找到元素的问题,这是因为DOM操作必须等待HTML文档加载完毕才可获取,而JS文件的引入一般放在<head>中,先加载了DOM操作。因此,需要用onload事件来加载HTML。
<div id="demo">test</div>
window.onload=function(){
if(document.getElementById){
var result=document.getElementById("demo");
alert(result);
}else{ //IE5.0以下不兼容getElementById
alert("您这浏览器不兼容,换掉吧~");
}
}
当通过getElementById()获取到特定元素节点时,这个节点对象就被我们获取到了,而通过这个节点对象,我们可以访问它的一系列属性。
(1)元素节点属性
属性 | 说明 |
tagName | 获取元素节点的标签名 |
innerHTML | 获取元素节点里的内容,非W3C DOM规范 |
window.onload=function(){
var result=document.getElementById("demo");
alert(result.tagName); //获取这个元素节点的标签名
alert(result.innerHTML); //获取这个元素节点里的文本内容(可包含HTML标签)
}
其中innerHTML属性不单纯只能获取元素节点里内容,亦可以进行赋值,解析HTML,
window.onload=function(){
var result=document.getElementById("demo1");
result.innerHTML="好好<strong>学习</strong>呀!";
}
(2)标签元素的通用属性
属性 | 说明 |
id | 元素节点的id名称 |
title | 元素节点的title属性值 |
style | CSS内联样式属性值 |
className | CSS元素的类 |
<div id="demo1" title="标题" class="demo2" style="color: red">test</div>
window.onload=function(){
var result=document.getElementById("demo1");
alert(result.id); //获取这个元素节点id属性的值,而不是属性节点
alert(result.title); //获取title属性的值
alert(result.style); //获取style属性对象
alert(result.style.color); //获取style属性对象中color属性的值
alert(result.className ) //获取class属性的值
}
2、getElementsByTagName()方法
该方法将返回一个对象数组HTMLCollection(NodeList),其中保存着所有相同元素名的节点列表。
window.onload=function(){
var li=document.getElementsByTagName("li"); //参数传递一个标签名
alert(li); //返回结果[object HTMLCollection],一个数组集合
alert(li.length); //返回li数组的数量
alert(li[0]); //返回结果[object HTMLCollection],li的节点对象
alert(li[0].tagName);//返回结果LI
alert(li[0].innerHTML);
}
选择通配符会输出HTML文档中所有节点,但各个浏览器之间有差异,
window.onload=function(){
var all=document.getElementsByTagName("*");
alert(all.length);
alert(all[0].tagName);
}
火狐浏览器的firebug打开后,会自动创建一个div,所以节点长度加1。IE浏览器比火狐和谷歌多一个节点,因为它把<!文档声明也算在其中。
与对象相关联的属性和方法
3、getAttribue(attribute)方法
getElementById()和getElementByTagName()检索特定元素节点,在找到特定元素之后,就可以用getAttribute()方法把它的各种属性的值查询出来。getAttribute()方法不能通过document对象调用,只能够通过一个元素节点对象调用它。
4、setAttribute()方法
setAttribute()方法允许对属性节点的值作出修改。setAttribute()方法也只能通过元素节点对象来调用,并需要传递两个参数,object.setAttribute(attribute,value)。当需要修改的属性值不存在时,setAttribute()方法调用实际上完成了两项操作:先把属性创建出来,然后在对其值进行设置。
通过setAttribute()方法对文档作出的修改,将使得文档在浏览器窗口里的显示效果/行为动作发生相应的变化,但在浏览器中查看源代码时却能看到原来的属性值,也就是说,setAttribute()方法作出的修改不会反映在文档本身的源代码里。这种现象是因为DOM的工作模式:先加载文档的静态内容,再以动态方式对它们进行刷新,动态刷新不影响文档的静态内容。
这正是DOM的真正威力和诱人之处:对页面内容的刷新不需要最终用户在他们的浏览器里执行页面刷新操作就可以实现。
——摘自《JavaScript+DOM 编程艺术》
三、DOM节点
1、node节点属性
节点可分为元素节点、属性节点和文本节点,而这些节点又有三个非常有用的属性,分别为:nodeName、nodeType和nodeValue。
信息节点属性
节点类型 | nodeName | nodeType | nodeValue |
元素 | 元素名称 | 1 | null |
属性 | 属性名称 | 2 | 属性值 |
文本 | #text | 3 | 文本内容(不包含html) |
<div id="demo">test</div>
window.onload=function(){
var result=document.getElementById("demo");
alert(result.nodeName); //返回DIV,获取元素节点的标签名,和tageName等价
alert(result.nodeType); //返回结果1,获取元素节点的类型值
alert(result.nodeValue); //返回null,素数节点本身没有内容
}
2、层次节点属性
节点的层次结构可以划分为:父节点与子节点、兄弟节点这两种。当获取其中一个元素节点的时候,就可以使用层次节点属性来获取它相关层次的节点。
层次节点属性
属性 | 说明 |
childNodes | 获取当前元素节点的所有子节点 |
firstChild | 获取当前元素节点的第一个子节点 |
lastChild | 获取当前元素节点的最后一个子节点 |
ownerDocument | 获取该节点的文档根节点,相当与document |
parentNode | 获取当前节点的父节点 |
previousSibling | 获取当前节点的前一个同级节点 |
nextSibling | 获取当前节点的后一个同级节点 |
attributes | 获取当前元素节点的所有属性节点集合 |
(1)childNodes属性
childeNodes属性可以获取某一个元素节点的所有子节点,这些子节点包含元素子节点和文本子节点,
<div id="demo"><strong>More</strong> haste, <em>less</em> speed.</div>
window.onload=function(){
var result=document.getElementById("demo");
alert(result.childNodes); //返回结果[object NodeList]
alert(result.childNodes.length); //4个子节点
}
第一个子节点为:<strong>More</strong>,这个节点称为:元素节点
第二个子节点为:haste,这个节点称为:文本节点
第三个子节点为:<em>less</em>,这个节点称为:元素节点
第四个子节点为:speed,这个节点称为:文本节点
window.onload=function(){
var result=document.getElementById("demo");
alert(result.childNodes[1]); //[object Text]表示一个文本节点对象
alert(result.childNodes[1].nodeType); //3,表示文本节点
alert(result.childNodes[1].nodeValue); // 获取文本节点的文本内容,返回结果haste,
alert(result.childNodes[1].innerHTML); //undefined,已经是文本内容再返回里面内容则无效
}
innerHTML和nodeValue的区别,
第一个区别就是取值的不同;第二个区别就是赋值的时候,nodeValue会把包含在文本里的HTML转义成特殊字符,从而达到形成单纯文本的效果。
window.onload=function(){
var result=document.getElementById("demo");
result.innerHTML="测试<strong>test</strong>"; //输出结果测试test(test加粗)
result.nodeValue="测试test"; //没有效果,nodeValue必须在当前节点上操作
result.childNodes[0].nodeValue="测试test"; //输出结果测试test
result.childNodes[0].nodeValue="测试<strong>test</strong>"; //返回结果测试<strong>test</strong>
(2)firstChild和lastChild属性
firstChild用于获取当前元素节点的第一个子节点,相当于childNodes[0];lastChild用于获取当前元素节点的最后一个子节点,相当于childNodes[box.childNodes.length - 1]。
<div id="demo">More <strong>haste</strong>, <em>less</em> speed.</div>
window.onload=function(){
var result=document.getElementById("demo");
// alert(result.childNodes[0].nodeValue);
// alert(result.childNodes[result.childNodes.length-1].nodeValue);
//以上方法太繁琐
alert(result.firstChild.nodeValue);
alert(result.lastChild.nodeValue);
}
(3)parentNode、previousSibling、nextSibling属性
parentNode属性返回该节点的父节点,
previousSibling属性返回该节点的前一个同级节点,
nextSibling属性返回该节点的后一个同级节点。
<div id="demo">More <strong>haste</strong>, <em>less</em> speed.</div>
window.onload=function(){
var result=document.getElementById("demo");
alert(result.parentNode); //返回结果[object HTMLBodyElement]
alert(result.firstChild.nextSibling); //[object HTMLElement]
alert(result.firstChild.nextSibling.nodeName); //STRONG,因为下一个同级节点为元素节点,所有可返回其标签名
alert(result.lastChild.previousSibling.nodeName); //EM
}
(4)attributes属性
attributes属性返回该节点的属性节点集合,
<div id="demo1" title="测试" class="demo2">Test</div>
window.onload=function(){
var result=document.getElementById("demo1");
alert(result.attributes); //[object NamedNodeMap]
alert(result.attributes.length); //3个属性
alert(result.attributes[0]); //[object Attr]
alert(result.attributes[0].nodeType); //2,属性节点的类型值
alert(result.attributes[0].nodeValue);
alert(result.attributes[0].nodeName);
}
对于后两个,不同浏览器存在差异,chrome中nodeValue为demo1,nodeName为id;IE中nodeValue为测试,nodeName为title。
(5)空白文本节点
<div id="demo1" title="测试" class="demo2">
<p>testOne</p>
<p>testTwo</p>
<p>testThree</p>
</div>
window.onload=function(){
var result=document.getElementById("demo1");
alert(result.childNodes.length); //7
}
最终输出的长度为7,而一眼看过去HTML中明明只有三个<p>元素,那为何如此呢?这就是因为计算了空白文本节点,譬如下图,这样的空白段有四处,
为此要解决空白文本节点的干扰问题,方法有二,
方式一:忽略空白字符
window.onload=function(){
var result=document.getElementById("demo1");
alert(filterSpaceNode(result.childNodes).length);
}
function filterSpaceNode(node){
var ret=[];
for(var i=0;i<node.length;i++){
if(node[i].nodeType===3 && /^\s+$/.test(node[i].nodeValue)){
continue;
}else{
ret.push(node[i]);
}
}
return ret;
}
方式二:移除空白字符
function removeSpaceNode(node){
for(var i=0;i<node.length;i++){
if(node[i].nodeType===3 && /^\s+$/.test(node[i].nodeValue)){
node[i].parentNode.removeChild(node[i]);
//找到空白字符就返回到其父节点,将其父节点的子节点删除,也就是删除了空白字符节点
}
}
return node;
}
window.onload=function(){
var result=document.getElementById("demo1");
alert(removeSpaceNode(result.childNodes).length);
}
实例:移除空白字符的应用
当使用firstChild、lastChild、previousSibling和nextSibling在获取节点的过程中遇到空白节点,返回结果就是一个空白,这是不想要的,譬如,
window.onload=function(){
var result=document.getElementById("demo1");
alert(result.firstChild.nodeValue);
alert(result.firstChild.nodeName); //返回结果#text
}
那该如何?那就需要移除父节点的空白字符咯,
function removeSpaceNode(node){
for(var i=0;i<node.childNodes.length;i++){
if(node.childNodes[i].nodeType===3 && /^\s+$/.test(node.childNodes[i].nodeValue)){
node.childNodes[i].parentNode.removeChild(node.childNodes[i]);
}
}
return node;
}
window.onload=function(){
var result=document.getElementById("demo1");
alert(removeSpaceNode(result).firstChild.nodeName); //返回结果P
}
四、节点操作
节点操作方法
方法 | 说明 |
write() | 这个方法可以把任意字符串插入到文档中 |
createElement() | 创建一个元素节点 |
appendChild() | 将新节点追加到子节点列表的末尾 |
createTextNode() | 创建一个文件节点 |
insertBefore() | 将新节点插入在前面 |
repalceChild() | 将新节点替换旧节点 |
cloneNode() | 复制节点 |
removeChild() | 移除节点 |
1、write()方法
把任意字符串插入到文档中去,
<div>
<p>testOne</p>
<p>testTwo</p>
<p>testThree</p>
</div>
window.onload=function(){
document.write("<p>Knowledge is a measure, but practise is the key to it.</p>");
document.write("<p>Lost time is never found again.</p>");
document.write("<p>Adversity reveals genius, fortune conceals it.</p>");
//将之前的内容覆盖掉
}
控制台中显示内容为修改过后的内容,原本内容被覆盖了,
2、createElement()方法
window.onload=function(){
document.createElement("p");
}
然而没有任何效果显示,是因为现在只是创建了元素节点p,并没有添加到文档中,只是驻留在内存中。
createElement()方法的兼容性问题
在几个特殊标签上,比如iframe、input中的radio和checkbox、button元素中,可能会在IE7以下的浏览器存在一些不兼容。前提是需要引用浏览器检测那篇文章中提到的browserdetect.js文件,
window.onload=function(){
var body=document.getElementsByTagName("body")[0];
if(BrowserDetect.browser=="Internet Explorer" && BrowserDetect.version<=7){
var input=document.createElement("<input type=\"radio\" name=\"sex\">");
}else{
var input=document.createElement("input");
input.setAttribute("type","radio");
input.setAttribute("name","sex");
}
body.appendChild(input);
}
3、appendChild()方法
将一个新节点添加到某个节点的子节点列表的末尾上,
<div id="test">
<p>testOne</p>
<p>testTwo</p>
<p>testThree</p>
</div>
window.onload=function(){
var demo=document.getElementById("test");
var elem=document.createElement("p");
demo.appendChild(elem);
}
最终效果是将新节点p添加到id=“test”的div中,但是里面并没有任何文本内容,
4、createTextNode()方法
创建一个文本节点,
window.onload=function(){
var demo=document.getElementById("test");
var elem=document.createElement("p");
demo.appendChild(elem);
var txt=document.createTextNode("testFour");
elem.appendChild(txt);
}
5、insertBefore()方法
把节点创建到指定节点的前面,
window.onload=function(){
var demo=document.getElementById("test");
var elem=document.createElement("p");
//demo.parentNode就是body
demo.parentNode.insertBefore(elem,demo);
}
insertBefore的参数,第一个是需要插入的新标签元素,第二个是当前插入位置的标签。但是,在指定元素之前直接插入会无效,需要在指定元素test的父元素body上添加一个新标签p,这样也就是在test前面添加一个元素节点,
已知了可以把节点创建在指定节点前面,那么想要将节点创建在指定节点后面如何做到?DOM中没有直接的方法,但之前有学习过一个操作方法,
appendChild() | 将新节点追加到子节点列表的末尾 |
<span>begin</span>
<div id="test">
<p>testOne</p>
<p>testTwo</p>
<p>testThree</p>
</div>
<span>end</span>
window.onload=function(){
var demo=document.getElementById("test");
var elem=document.createElement("p");
demo.parentNode.appendChild(elem);
}
然而,这新建的元素插到了<body>元素的最后,并没有如预期要求的在指定test元素后紧接着创建新元素,所以需要自定义一个insertAfter()函数。
function insertAfter(newElement,targetElement){
//此处父节点就是body
var parent=targetElement.parentNode;
if(parent.lastChild===targetElement){
parent.appendChild(newElement,targetElement);
}else{
//span节点前面添加,可以用inserBefore
parent.insertBefore(newElement,targetElement.nextSibling);
}
}
window.onload=function(){
var demo=document.getElementById("test");
var elem=document.createElement("p");
insertAfter(elem,demo);
}
6、replaceChild()方法
把指定节点替换成新的目标节点,
window.onload=function(){
var demo=document.getElementById("test");
var elem=document.createElement("p");
demo.parentNode.replaceChild(elem,demo);
}
7、cloneNode()方法
把子节点复制出来,但这里会有空白字符问题的干扰,
window.onload=function(){
var demo=document.getElementById("test");
var clone=removeSpaceNode(demo).firstChild.cloneNode(true);
demo.appendChild(clone);
}
function removeSpaceNode(node){
for(var i=0;i<node.childNodes.length;i++){
if(node.childNodes[i].nodeType===3 && /^\s+$/.test(node.childNodes[i].nodeValue)){
node.childNodes[i].parentNode.removeChild(node.childNodes[i]);
}
}
return node;
}
其中,cloneNode中参数有true和false,true时会克隆标签和文本内容,而false只会克隆标签,
8、removeChild()方法
删除指定节点,
window.onload=function(){
var demo=document.getElementById("test");
demo.removeChild(removeSpaceNode(demo).firstChild); //删除第一个<p>标签,这儿仍会有空白字符问题的干扰
demo.parentNode.removeChild(demo); //删除整个test
}
function removeSpaceNode(node){
for(var i=0;i<node.childNodes.length;i++){
if(node.childNodes[i].nodeType===3 && /^\s+$/.test(node.childNodes[i].nodeValue)){
node.childNodes[i].parentNode.removeChild(node.childNodes[i]);
}
}
return node;
}