Go to comments

JavaScript DOM 5个练习题

课堂练习

1. 遍历元素节点树,要求不能用children属性

2. 封装函数,返回元素e的第n层祖先元素

3. 封装函数,返回元素e的第n个兄弟元素节点,n为正,返回后面的兄弟元素节点,n为负,返回前面的,n为0,返回自己。

4. 编辑函数,封装children功能,解决以前部分浏览器的兼容性问题

5. 自己封装hasChildren()方法,不可用children属性


1、遍历元素节点树(在原型链上编程),要求不能用children属性。

遍历元素节点树分为两种提议:

1. 第一种是简单的,

    比如给一个父节点,把它的子节点都给遍历出来,

    什么是便利?全部查找出来并且打印出来,这是最简单的方法用 childNodes 挨个打印。

2. 第二种就真正要打印出一个树形结构,

    比如给你一个节点树(树型结构)把div里面的层级结构全打印出来

    第一层 p、span

    第二层 strong、b

<div>
  <p></p>
  <span>
    <strong></strong>
    <b></b>
  </span>
</div>


先看 div 子元素节点打印出来,然后再挨个判断这些子元素节点还有没有子元素节点,有就一直往下打印,递归...

<div>
  <p></p>
  <span>
    <strong></strong>
    <b></b>
  </span>
  <i>
    <strong>
      <address></address>
      <sub></sub>
    </strong>
    <b></b>
  </i>
</div>

<script>

  Element.prototype.retChildren = function(num){
    var child = this.childNodes
    var len = child.length;
    var str = '';
    num++;

    for(var i =0; num > i; i++){
      if(num == 1){
        str = '';
      }else{
        str+='-';
      }
    }

    for(var i = 0; len > i; i++){
      if(child[i].nodeType == 1){
        document.write(str + num + ": " + child[i].nodeName + '<br/>');
        if(child[i].hasChildNodes()){
          child[i].retChildren(num);
        }
      }
    }
  }

  var oDiv = document.getElementsByTagName('div')[0];

  console.log(oDiv.retChildren(0));

</script>


2. 封装函数,返回元素e的第n层祖先元素。

把 i 标签给我们,返回 i 标签的第二层祖先(父级)元素节点,第一层是 span、第二层是 strong。

<div>
    <b></b>
    abc
    <!-- this is comment -->
    <strong>
        <span>
            <i></i>
        </span>
    </strong>
    <em></em>
</div>

封装一个retParent方法把元素传进去,把第几个祖先节点传进去,传进去之后函数运算完要返回这个结果。

/**
 * retParent(elem, n)
 *
 * elem   object  元素节点
 * n      int     第几个父亲级节点
 */

function retParent(elem, n){

}

n是多少就有多少个parentElement层,基于n为循环条件,每次循环操作让elem等于它的父级,最后一次把elem返回。

<div>
    <b></b>
    abc
    <!-- this is comment -->
    <strong>
        <span>
            <i></i>
        </span>
    </strong>
    <em></em>
</div>

<script>

    function retParent(elem, n){
        
        while(n){
            elem = elem.parentElement;
            n --;
        }
    
        return elem;
        
    }
    
    var i = document.getElementsByTagName('i')[0];
    
    console.log(retParent(i ,2)); // strong
    console.log(retParent(i ,3)); // div
    console.log(retParent(i ,0)); // 0不进入循环,直接返回它自己
    
    console.log(retParent(i ,10)); // Uncaught TypeError: Cannot read property 'parentElement' of null
    
</script>

如果参数n填10就报错了,因为没有第10个到第6个父级就是null了,null没有parentElement怎么做一个容错?

看报错信息"Cannot read property parentElement of null"也就是说往上求父级,一层层求到最后有一个null了,null调用parent.Elment就报错了,判断一下如果elem等于null了就结束。

<div>
    <b></b>
    abc
    <!-- this is comment -->
    <strong>
        <span>
            <i></i>
        </span>
    </strong>
    <em></em>
</div>

<script>

	function retParent(elem, n){

		while(elem && n){  // 如果elem等于null就没有意义了,就结束返回null就行了

			elem = elem.parentElement;
			n --;
		}

		return elem;

	}

	var i = document.getElementsByTagName('i')[0];

    console.log(retParent(i ,5));  // 到5是html
    console.log(retParent(i ,6));  // 到6就是null了,是null"while(elem && n)"并且关系不成立,直接退出了
    console.log(retParent(i ,10)); // 6以上返回都是null了
    
</script>

4、编辑函数,封装children功能,解决以前部分浏览器的兼容性问题。

chilren兼容性其实挺好,这个题的意思是用我们自己的方法实现chilren的功能。

chilren的功能是找该元素的"子元素节点"不是"子点节","子节点"五花八门什么都有,现在就要"子元素节点",而且不用children来实现children的功能。

封装一个MyChildren()方法,在原型链上编写。

Element.prototype.MyChilren = function(){

    return ...;
    
}

"oDiv.MyChildren()"调用这个方法就能返回"子元素节点"的集合,不能chilren用childNodes代替

<div>
    <b></b>
    abc
    <!-- this is comment -->
    <strong>
        <span>
            <i></i>
        </span>
    </strong>
    <em></em>
</div>

<script>

    Element.prototype.MyChilren = function(){

        var child = this.childNodes;
        var len = child.length;
        var arr = [];
        
        for(var i = 0; i < len; i ++){
            if(child[i].nodeType == 1){  // 挑出元素节点
                arr.push(child[i]);
            }
        }

        return arr;
    }


    var oDiv = document.getElementsByTagName('div')[0];

    console.log(oDiv.MyChilren()); // [b, strong, em]

</script>

这题考的是如何区分"元素节点"和"非元素节点"。

5、自己封装hasChildren()方法,不可以用children属性。

有一个Node.hasChildNodes()方法,有childNodes返回true没有返回false。

这个hasChildren()基于上面的MyChildren()简单改一下,先用childNodes然后还是循环看看有没有元素节点,如果有元素节点直接返回true没有返回false。

<div>
    <b></b>
    abc
    <!-- this is comment -->
    <strong>
        <span>
            <i></i>
        </span>
    </strong>
    <em></em>
</div>

<script>

    Element.prototype.MyHasChildren = function(){

        var child = this.childNodes;
        var len = child.length;
        var arr = [];

        for(var i = 0; i < len; i ++){
            if(child[i].nodeType == 1){
                return true;
            }
        }

        return false;
    }

    var b = document.getElementsByTagName('b')[0];
    console.log(b.MyHasChildren()); // p标签内没有元素返回false

    var strong = document.getElementsByTagName('strong')[0];
    console.log(strong.MyHasChildren()); // strong标签有元素返回true

</script>

3、封装函数,返回元素e的第n个兄弟元素节点,n为正,返回后面的兄弟元素节点,n为负,返回前面的,n为0,返回自己。

意思是封装一个函数"retSibling(e, n)",参数e是传进去的元素,参数n是元素的第几个"兄弟元素节点",n为正返回后面的,n为负返回前面的,n为0返回自己。

function retSibling(e, n){

}

基本思路和"返回元素的第n层祖先元素"差不多,祖先节点是parentElement,兄弟节点是nextSibling、nextElementSibling、previousSibling、previousElementSibling这些东西,然后循环调用。

由于n分正负,分正负就有两种处理方法,为正的是一种循环方法,为负的是一种循环方法写两个循环。

function retSibling(e, n){

    if(n > 0){
        while(n){
            n --
        }
    }else{
        while{
            n ++
        }
    }

}

常规结构是这么写的,但是现在不这么写。

用一个简单点的,先写white循环,以n作为循环基数,n为0的时候循环退出,然后在white里面判断。

function retSibling(e, n){
    while(n){
        if(n > 0){
            n --;  // n大于0 n--
        }else{
            n ++;  // n小于0 n++
        }
    }
}

这样一个while循环就能解决所有的问,在wilhe里面处理问题就不用写两个循环了,写起来效率更高。

不考虑兼容性的问题

function retSibling(e, n){
    while(n){
        if(n > 0){
            e = e.nextElementSibling; 
            n --; // n大于0肯定n--不然怎么循环呢
        }else{
            e = e.previousElementSibling;
            n ++;
        }
    }
    return e; // 最后返回这个e
}

写到这先不测试,肯定是有一点问题的,比如n特别小没事如果n超大呢,n到边界的时候会有null,"null.nextElementSibling"肯定会报错,这块要加一个兼容?

<div>
    <span></span>
    <p></p>
    <strong></strong>
    <!-- this is comment -->
    <i></i>
    <address></address>
</div>


<script>

    function retSibling(e, n){
        while(e && n){  // 这加一个 e
            if(n > 0){
                e = e.nextElementSibling;
                n --;
            }else{
                e = e.previousElementSibling;
                n ++;
            }
        }
        return e; // 最后返回这个 e
    }

    var strong = document.getElementsByTagName('strong')[0];

        console.log(retSibling(strong, 1));     // <i></i>
        console.log(retSibling(strong, 2));     // <address></address>
        console.log(retSibling(strong, 4));     // null
        console.log(retSibling(strong, 10000)); // null
        console.log(retSibling(strong, -1));    // <p></p>
        console.log(retSibling(strong, -2));    // <span></span>
        console.log(retSibling(strong, -3));    // null
        console.log(retSibling(strong, -300));  // null
        console.log(retSibling(strong, 0));     // 0是自己没处理就直接就跳出循环,返回的是自己<strong></strong>

</script>

现在在这个基础上加深点难度,nextElementSibling和previousElementSibling这两个兼容性不好,IE9以下不能用,如果要在IE9以下要实现这个功能怎么办?

还有一个方法是"nextSibling",但是nextSibling求的是下一个"兄弟节点",但是我们要求的是"兄弟元素节点"

<div>
    <span></span>
    <p></p>
    <strong></strong>
    <!-- this is comment -->
    <i></i>
    <address></address>
</div>


<script>

function retSibling(e, n){
    while(e && n){
        if(n > 0){
            if(0 && e.nextElementSibling){
                e = e.nextElementSibling;
                // 判断如果IE9以上还用nextElementSibling
                // 加一个控制"if(0 && e.nextElementSibling)",0判断强制程序走else下面的for循环
            }else{

                // 如果IE9以下写兼容
                // 每一圈while循环都要找到下一个兄弟节点,"e.nextSibling"找的不是"元素节点"

                // for循环 
                // 第一步 e = e.nextSibling找的是兄弟节点
                // 第二步 判断"e.nodeType != 1",不等于1说明不是元素节点,不是元素节点要继续e = e.nextSibling;

                // e = e.nextSibling可以写大括号里
                // for(1. e = e.nextSibling; 2.e.nodeType != 1){
                //    3. e = e.nextSibling
                // }
                                                        // e = e.nextSibling写这也行
                for(e = e.nextSibling; e.nodeType != 1; e = e.nextSibling);

                // 然后for循环的执行体里面什么都不用写,如果for循环执行体里是空,这个执行体可以直接去掉,在后面加一个分号就可以。
                // for和if可以不用写执行体,加分号就行。
            }
            n --;
        }else{
            e = e.previousElementSibling;
            n ++;
        }
    }
    return e;
}

var strong = document.getElementsByTagName('strong')[0];

// 看一下不加负数加正数
console.log(retSibling(strong, 1)); // <i></i>
console.log(retSibling(strong, 2)); // <address></address>
console.log(retSibling(strong, 3)); // 加3出问题了 Uncaught TypeError: Cannot read property 'nodeType' of null at retSibling

</script>

为什么到"3"会报错?找到address后"2"就结束了,address后面是文本节点,文本节点后面没有东西了,没东西了是null,往下会判断"null.nodeType != 1",null没有nodeType肯定会报错Cannot read property 'nodeType'。

要加一个容错判断一下e就完事了,如果e是null就停止。

<div>
    <span></span>
    <p></p>
    <strong></strong>
    <!-- this is comment -->
    <i></i>
    <address></address>
</div>

<script> 

function retSibling(e, n){
    while(e && n){
        if(n > 0){
            if(0 && e.nextElementSibling){
                e = e.nextElementSibling;
            }else{
                for(e = e.nextSibling; e && e.nodeType != 1; e = e.nextSibling);
                                       // 这里判断一下e如果是null就别看"e.nodeType != 1",直接结束了
            }
            n --;
        }else{
            e = e.previousElementSibling;
            n ++;
        }
    }
    return e;
}


var strong = document.getElementsByTagName('strong')[0];

console.log(retSibling(strong, 1)); // <i></i>
console.log(retSibling(strong, 2)); // <address></address>
console.log(retSibling(strong, 3)); // null
console.log(retSibling(strong, 4)); // null

</script>

完善这个函数

<div>
    <span></span>
    <p></p>
    <strong></strong>
    <!-- this is comment -->
    <i></i>
    <address></address>
</div>

<script> 

    function retSibling(e, n){
        while(e && n){
            if(n > 0){
                if(e.nextElementSibling){
                    e = e.nextElementSibling;
                }else{
                    for(e = e.nextSibling; e && e.nodeType != 1; e = e.nextSibling);
                }
                n --;
            }else{
                if(e.previousElementSibling){
                    e = e.previousElementSibling;
                }else{
                    for(e = e.previousSibling; e && e.nodeType != 1; e = e.previousSibling);
                }
                n ++;
            }
        }
        return e;
    }


    var strong = document.getElementsByTagName('strong')[0];

    console.log(retSibling(strong, 1)); // <i></i>
    console.log(retSibling(strong, 2)); // <address></address>
    console.log(retSibling(strong, 3)); // null
    console.log(retSibling(strong, 4)); // null

    console.log(retSibling(strong, -1)); // <p></p>
    console.log(retSibling(strong, -2)); // <span></span>
    console.log(retSibling(strong, -3)); // null
    console.log(retSibling(strong, -4)); // null

</script>


Leave a comment 0 Comments.

Leave a Reply

换一张