javascript - 练习题:DOM基本操作
练习1:
遍历元素节点树;
<div>
<p>
<span>
<i></i>
</span>
</p>
<p></p>
</div>
分析:递归
使用 children 是返回当前元素的元素子节点;
每个子节点下面还有节点要遍历,遍历多少圈我们不知道;
这样就要用递归;
function trav(elem) {
console.log(elem.nodeName);
var children = elem.children,
len = children.length;
for (var i = 0; i < len; i++) {
trav(children[i]);
}
}
// var div = document.getElementsByTagName('div')[0];
trav(document.body);
可以取 div ,也可以输出 document.body
练习2:
封装函数,返回元素e的第n层祖先元素节点;
<div>
<p>
<span>
<i></i>
</span>
</p>
</div>
分析:以i为例;
i 的第一层祖先是 span; 第二层祖先是 p;第三层祖先是 div;
第一层祖先用 e.parentElement ;
第二层祖先是 e.parentElement.parentElement,可以把 e.parentElement 赋值给 e,再取 e.parentElement 就行了,一直循环直到 n 次;
第n层祖先总会到头,最顶层的祖先一定是 null ,所以再来个条件判断当 e 的祖先是 null 时要退出循环;
var i = document.getElementsByTagName('i')[0];
function retParentElem(elem,n){
while(elem && n){ // elem 为null 时,也退出循环
elem = elem.parentElement; // 把 elem 的祖先元素再赋值给 elem
n--; // n 递减
}
return elem;
}
控制台:
n == 1,2,5 时,都能正常返回祖先元素;
n >= 6时,返回null;
n == 0时,返回自己;
练习3:
封装函数:myChildren功能,解决以前部分浏览器的兼容性问题;
<div>
<p>
<span>
<i></i>
</span>
</p>
<p></p>
<span></span>
</div>
children 方法意思是找当前元素的子元素节点,children的兼容性很好,这里仅用于练习;
分析:以 div 为当前元素,练习封装 myChildren;
1,定义到原型上去: Element.prototype.myChildren;
2,取当前元素的所有子节点, childNodes;
3,挨个判断当前子节点的 nodeType ,等于1的就留下;
Element.prototype.myChildren = function() {
var tempObj = { // 把返回值包装成类数组;
length: 0,
push: Array.prototype.push,
splice: Array.prototype.splice,
},
temp = this.childNodes,
len = temp.length;
for (var i = 0; i < len; i++) {
if (temp[i].nodeType == 1) {
tempObj.push(temp[i]);
}
}
return tempObj;
}
验证一下:
var div = document.getElementsByTagName('div')[0];
控制台输出:
练习4:
封装 hasChildren()方法,不可用children属性;
以这段HTML示例:
<div>
<p>
<span></span>
</p>
</div>
分析:判断当前节点有没有元素节点,返回布尔值;
参考上一题,改一下:
Element.prototype.hasChilden = function () {
child = this.childNodes;
len = child.length;
var arr = [];
for (var i = 0; i < len; i++) {
if (child[i].nodeType == 1) {
return true;
}
}
return false;
}
做个验证:
var div = document.getElementsByTagName('div')[0];
var span = document.getElementsByTagName('span')[0];
练习5:
封装函数,返回元素e的第n个兄弟元素节点,n为正,返回后面的兄弟元素节点,n为负,返回前面的,n为0,返回自己。
<div>
<p></p>
<em></em>
<span></span>
<strong></strong>
<i></i>
</div>
分析:
当 n 为正的时候,返回第 n 个后面的兄弟元素节点。
可以 while 循环n次,当n递减到0时,这时的 nextElementSibling 就是要返回的元素节点了。
还要做容错,如果n太大或太小,没有那么多兄弟节点的时候。
一直取 nextElementSibling 直到取不到的时候会返回 null , 可以拿这个 null 来做条件判断。
function retElem(e, n) {
while (e && n) { // 容错
if (n > 0) {
e = e.nextElementSibling;
n--;
}
if (n < 0) {
e = e.previousElementSibling;
n++;
}
}
return e;
}
看下控制台输出:
这样就都满足要求了。
不过 nextElementSibling 的兼容性不是很好,所以可以使用 nextSibing 来做兼容,但 nextSibling 不仅返回元素节点,还会返回其他的节点;
所以要在一个循环里:解决每一个都是元素节点的问题;
function retElem(e, n) {
while (e && n) {
if (n > 0) {
if (e.nextElementSibling) { // 可以用 0 && e.nextElementSibling 来跳过 if ,执行 else 的语句
e = e.nextElementSibling;
} else {
e = e.nextSibling;
while (e && e.nodeType != 1) { // 拿e来做容错了,如果不加e,遇到 null的时候会报错,因为 null 没有nodeType属性;
e = e.nextSibling;
}
}
n--;
}
if (n < 0) {
if(e.previousElementSibling){
e = e.previousElementSibling;
}else{
e = e.previousSibling;
while (e && e.nodeType != 1) {
e = e.previousSibling;
}
}
n++;
}
}
return e;
}
注意这句条件判断:
只有当 e 和 e.nodeType ! =1 同时成立,才会执行,否则, e = e.nextSibling 取到 null 时会报错,因为 null 没有 nodeType 属性;
另外,要跳过 if 直接执行 else 的执行,可以让 if 短路。 0 && e.nextElementSibling 。
练习6:
封装函数 insertAfter(); 功能类似 insertBefore()
先了解一下 insertBefore();
要在 p 标签前面插入一个新元素,可以:
var div = document.getElementsByTagName('div')[0];
var p = document.getElementsByTagName('p')[0];
var i = document.createElement('i');
div.insertBefore(i,p);
现在要封装 insertAfter() ,意思是把新建的元素节点插在指定元素后面;
Element.prototype.insertAfter = function(targetNode,afterNode){
// afterNode.nextElementSibling ? this.insertBefore(targetNode,afterNode.nextElementSibling) : this.appendChild(targetNode);
if(afterNode.nextElementSibling){
this.insertBefore(targetNode,afterNode.nextElementSibling);
}else{
this.appendChild(targetNode);
}
}
var div = document.getElementsByTagName('div')[0];
var p = document.getElementsByTagName('p')[0];
var i = document.createElement('i'); // 新建的 i 元素
div.insertAfter(i,p) // 把i 插在 p 后面;
也可以用一个三目运算,一行就可以了;
练习7:
将目标节点内部的节点顺序逆序。
<div><a></a><em></em></div> --> <div><em></em><a></a></div>
封装在原型上:
Element.prototype.elemReverse = function () {
for (var i = this.children.length - 2; i >= 0; i--) {
this.appendChild(this.children[i]);
}
}
var div = document.getElementsByTagName('div')[0];
div.elemReverse();