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;
}

控制台:

javascript - 练习题:DOM基本操作_javascript

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];

控制台输出:

javascript - 练习题:DOM基本操作_元素节点_02


练习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];

javascript - 练习题:DOM基本操作_元素节点_03



练习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;
}

看下控制台输出:

javascript - 练习题:DOM基本操作_元素节点_04

这样就都满足要求了。

不过  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.nodeType ! =1 同时成立,才会执行,否则,  e = e.nextSibling 取到 null 时会报错,因为 null 没有 nodeType 属性;

另外,要跳过 if 直接执行 else 的执行,可以让 if 短路。 0 && e.nextElementSibling 。


练习6:

封装函数 insertAfter(); 功能类似 insertBefore()

先了解一下 insertBefore(); 

<div>
<p></p>
</div>

要在 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();