对于DOM来说,知识点其实并不多,要理解DOM并不难,难的是会用。可能有的人看见DOM获取元素要这么长一串单词就觉得生无可恋了。不过说实在的,如果你能理解他的意思跟用法。而且稍微再有点英语基础的话,DOM其实还是很简单的。而对于觉得自己英语不好的人来说,不用想了,多打代码是你唯一的出路,程序员练得就是手感。
上一篇文章中,我主要讲了下一些事件,还有一些节点的操作。不过对于节点的层次只是大概的提了一下,这里就对节点的层次进行一个祥述。
在我们的DOM模型中,是由节点组成的,节点可以是标签,属性和文本。对于我们来说,一眼就能看出一个节点是什么节点。可是,在js中,要区分节点的话就要用到它的方法了
节点 |
nodeType |
nodeName |
nodeValue |
标签 |
1 |
对应的标签名 |
null |
属性 |
2 |
对应的属性名 |
对应的属性值 |
文本 |
3 |
#text |
对应的文本内容 |
nodeType方法是用来获取节点类型的。对应上表,标签的节点类型为1。属性的节点类型为2。而文本的节点类型则为3。通过nodeType方法我们可以知道一个节点是什么类型的。止于后面两种方法,则分别是用来获取节点的名字和节点的值的。他们所对应的值我在表格中都有描述,所以这边就不多提了。那具体要怎么来获取节点的类型呢?
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <div class="box"> <ul id="list"> <li>111111</li> <li>222222</li> <li id="li3">333333</li> <li>444444</li> </ul> </div> <script> var li3=document.getElementById("li3"); console.log(li3.nodeType); console.log(li3.nodeName); console.log(li3.nodeValue); </script> </body> </html>
以上述代码为例,他输出的值分别是1,LI,null。
在DOM中,还有一些方法可以获得自己的父辈节点和子代节点。可能有人好奇为什么要这么做,不是可以直接添加一个id名或者直接获取标签名不就好了嘛,为什么还要这么麻烦有这么多的方法让我们来记。写过静态页面的人肯定就不会这么说了,因为,在一个页面中标签实在是太多了,如果每用一个标签就要取一个名字的话,很容易造成重名,要不就是名字命名的很长。这些都会带来很多的不变,所以,就有了DOM中的方法来获取这些跟当前节点有的父辈,子代,或者兄弟节点。
首先来讲讲获取父辈节点,上述代码中,我们如果要获取id为li3这个节点的父辈节点的话,我们就可以使用
var li3=document.getElementById("li3"); console.log(li3.parentNode);
这样,我们就可以获得id为li3的这个节点的父辈节点了,这个方法在任何浏览器中都适用,而且没有兼容性问题。
然后就是获取子代节点了,同样的以上述代码为例。比如我们要获取ul这个标签的子代节点,我们就有两种方式了。一种是
var list=document.getElementById("list"); var lis=li.childNode; for(var i=0;i<lis.length;i++){ console.log(lis[i]); }
这种方法在不同的浏览器就会有不同的效果了,在谷歌,火狐都中,不但可以获得标签节点,还可以获得文本节点,这里所说的文本节点,就是每个li标签之间的换行而产生的文本节点。而在IE8及之前的浏览器中则会忽略空白文本,也就是只会获取到标签节点。所以还有另一种获取的方式:
var list=document.getElementById("list"); var lis=li.children; for(var i=0;i<lis.length;i++){ console.log(lis[i]); }
这种方法并不是DOM中的方法,不过所有的浏览器都支持这种方法,并且他会获得所有的标签子节点。因为他的兼容性好,并且简单。所以在获取子节点的时候更提倡的使用到这个方法。
如果说获取子节点这种方法还算运气好还有一个替代的方法的话,接下来要说的几个方法兼容性就不那么尽如人意了。之前说了,节点之间除了父子关系外,还有兄弟关系。这里说的兄弟关系,也就是同级的节点之间的关系。所以,兄弟节点也分前一个节点和后一个节点。就拿之前那段代码来说id为li3的节点的前一个兄弟节点为<li>222222</li>,它的后一个兄弟节点为<li>444444</li>。那要如何获取他们呢?先说说获取前一个兄弟节点。我们可以用以下方法。
var li3 = document.getElementById("li3"); console.log(li3.previousSibling);
因为节点有三部分组成 ,标签 ,属性,文本,而previousSibling所获取的不仅仅是标签节点,还能够获取文本节点,这种方法所有浏览器都是支持的,只是IE8及之前的浏览器会忽略空白文本节点。
因为获取到的空白节点没有用,所以就有另一种直接获取前一个标签兄弟节点。
var li3 = document.getElementById("li3"); console.log(li3.previousElementSibling);
根据语义,就可以知道是前一个元素节点。他在谷歌跟火狐中都是能直接获取到前一个元素节点,而ie8及之前的版本却不支持这种方法。所以,为了在各个版本中都能兼容,我们就要封装一个函数来使得各个浏览器都能兼容。
function getPreviousSibling(obj){ if(obj.previousElementSibling){ return obj.previousElementSibling; }else { var node = obj.previousSibling; while(node&&node.nodeType!=1){ node = node.previousSibling; } return node; } }
这里,我们定义了一个getPreviousSibling的函数用来获取前一个标签兄弟节点,这个函数有一个参数用来传入要获取哪个标签的前一个标签节点。在函数中,我们就用到了能力检测,也就是这里的if else判断。如果obj.previousElementSibling这个方法存在,那么返回obj.previousElementSibling。如果这个方法不存在,那么执行else里的代码。在else中,我们定义了一个变量node等于obj.previousSibling。这个方法是所有浏览器都兼容的。然后进入while循环,循环的成立的条件时node,也就是obj.previousSibling存在,并且它的nodeType不等于1,这句代码的意思就是如果这个要判断的节点的前一个兄弟节点存在并且不是标签节点的时候,那么进入到循环中来,让node等于obj.previousSibling也就是再之前的兄弟节点。一直到没有前一个兄弟节点了,或者前一个兄弟节点的节点类型不为1的时候,跳出循环,此时,node的值就是要操作的节点的前一个标签节点。把这个值返回。所以这段代码就完成了在任何浏览器中都能获取到要操作节点的前一个标签节点了。
我们调用一下他,并在控制台打印它的值。
console.log(getPreviousSibling(li3));
在google浏览器中会显示<li>222222</li>,而在ie8中则是显示 [object HTMLLIElement] ,这只是不同浏览器显示的问题,但这个结果说明我们封装的函数是有用的,能在低版本浏览器中起作用的。如果看明白了获取前一个标签兄弟节点的原理的话,之后的就不会很简单了。获取后一个标签兄弟节点跟这个是一模一样的。唯一不同的就是他获取后一个兄弟节点是
var li3 = document.getElementById("li3"); console.log(li3.nextSibling);
而获取后一个标签兄弟节点是:
var li3 = document.getElementById("li3"); console.log(li3.nextElementSibling);
他们有着相同的兼容性问题,所以我们也可以按照上面的方法封装一个函数来使得他兼容各个浏览器。
function getNextSibling(obj){ if(obj.nextElementSibling){ return obj.nextElementSibling; }else { var node = obj.nextSibling; while(node&&node.nodeType!=1){ node = node.nextSibling; } return node; } } console.log(getNextSibling(li3));
我们在控制台中打印一下它。在google浏览器中会显示<li>444444</li>,而在ie8中则是显示 [object HTMLLIElement] 。
与这四个方法相似的还有四个方法。分别是获取第一个子元素和最后一个子元素。获取第一个子元素也有两种方法。
var ul=document.getElementById("list"); console.log(ul.firstChild);
和
var ul=document.getElementById("list"); console.log(ul.firstElementChild);
前一中方法是获取第一个子元素,包括文本节点,后一种方法则是获取第一个标签子元素。跟之前一样,他们也是有一样的简永兴问题。所以要封装到一段函数中。
function getFirstChild(obj){ if(obj.firstElementChild){ return obj.firstElementChild; }else { var node = obj.firstChild; while(node&&node.nodeType!=1){ node = node.nextSibling; } return node; } }
与之相对应,还有一个就是获取最后一个子元素。他也有两种方法分别能获取最后一个子元素和最后一个标签子元素。分别是
var ul=document.getElementById("list"); console.log(ul.lastChild);
和
var ul=document.getElementById("list"); console.log(ul.lastElementChild);
给他们封装一个方法来兼容各个版本的浏览器。
function getLastChild(obj){ if(obj.lastElementChild){ return obj.lastElementChild; }else { var node = obj.lastChild; while(node&&node.nodeType!=1){ node = node.previousSibling; } return node; } }
好了,总算把这些绕来绕去的方法说完了,看着好像很烦,不过总结一下,找到规律之后发现还是很好记的。从获取兄弟节点开始,我一共提了八种方法,他们实际实现的功能其实只有四种,分别是获取下一个兄弟节点,获取上一个兄弟节点和获取第一个子元素,获取最后一个子元素每个功能有两种方法,这两种方法一种是会获取到文本节点的,但是所有浏览器都兼容,只是对于ie8及以下版本来说他是忽略文本节点的,所以获取到的就是元素节点。而另一种方法则是能直接获取到元素节点的,只是这个方法ie8及以下的版本都不兼容。所以就有了封装兼容性函数。这么一理,整个思路就清晰多了,再回头看的时候就不会感觉那么绕了。
好了,接下来就提一下另一个知识点,就是元素的克隆和追加。
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <ul id="list"> <li>111111</li> <li>222222</li> <li id="li3">333333</li> <li>444444</li> </ul> <script> var ul = document.getElementById("list"); var li = document.getElementById("li3"); console.log(li.cloneNode(false)); console.log(li.cloneNode(true)); console.log(ul.cloneNode(false)); console.log(ul.cloneNode(true)); </script> </body> </html>
在上述代码中,我们通过id的方式获取到了ul标签和id名为li3的li标签。我们克隆一个li标签并且在空中台中输出它,在这里,克隆节点我们用的是li.cloneNode();括号内的参数是用来选择克隆深度的,这里填的是一个boolean值,如果这个值为true的时候,那么就是深度克隆,他会克隆这个标签中所有的内容包括他的文本内容和子代标签。而值为false的时候,他克隆的则仅仅是一个标签,而不会有内容。
上述代码在google中输出的结果为
我们可以看到前三行为前面三行代码输出的值,第四行及之后的值则是第四行输出的内容。
前面我说了克隆和追加,既然我们克隆了这个节点,要怎么追加呢?比如我们如何把克隆的li加到原有的ul中呢?这时候,我们就要用到appendChild方法了。我们把克隆的li赋值给一个变量,然后把他克隆到ul中,具体代码如下
var newLI= li.cloneNode(true); ul.appendChild(newLI);
注意,appendChild放法是把克隆的节点放到了ul的最后面。所以,在页面中我们可以看到
这样,我们就完成了元素的克隆和追加。