DOM节点操作

607 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第11天,点击查看活动详情

DOM节点操作

概述

通常获取元素通常使用的两种方式:

  1. 利用DOM提供的方法获取元素:
    缺点是比较繁琐,逻辑性不强,就拿下面的来举例子,获取div要写一次,获取span要写一次,获取ul中的小li要先获取ul,然后再.quertSelectorAll('li')
    <div>我是div</div>
    <span>我是span</span>
    <ul>
        <li>我是小li</li>
        <li>我是小li</li>
        <li>我是小li</li>
        <li>我是小li</li>
    </ul>
  1. 利用节点层级关系获取元素
  • 利用父子兄节点关系获取元素
  • 逻辑性强,但兼容性稍差

从DOM树来看,网页中所有的内容都是节点。元素就叫元素节点,文档就叫文档节点、还有属性节点文本节点。一般地,节点至少拥有nodeType(节点类型)nodeName(节点名称)nodeValue(节点值) 这三个基本属性

示例

        var div = document.querySelector('div');
        console.dir(div); //在里面可以找到节点的三个基本属性

元素节点 nodeType 为1
属性节点 nodeType 为2
文本节点 nodeType 为3(文本节点包含文字、空格、换行等)

1.父节点 parentNode

    <div class="demo">
        <div class="box1">
            <div class="box2"></div>
        </div>
    </div>

正常情况,如果要操作box1和box2这两个盒子需要通过DOM提供的方法获取两次元素
var box1 = document.querySelector('.box1');
var box2 = document.querySelector('.box2');
...其他操作
可以看到我们获取了两次,但是我们可以通过节点操作简化代码:
var box2 = document.querySelector('.box2');
这时候可以通过 box2.parentNode; 来操作box2的父元素
注意:parentNode获取的是离该元素最近的父级节点

2.子节点操作

见如下结构:

    <ul>
        <li>我是小li</li>
        <li>我是小li</li>
        <li>我是小li</li>
        <li>我是小li</li>
    </ul>
    <ol>
        <li>我是小li</li>
        <li>我是小li</li>
        <li>我是小li</li>
        <li>我是小li</li>
    </ol>

以前我们获取一些子元素是用DOM提供的方法(API)获取
var ul = document.querySelector('ul');
var lis = ul.querySelectorAll('li');
现在利用节点操作:

(1) 父节点.childNodes

    console.log(ul.childNodes); //NodeList(9)

ul中明明只有4个li,却得到9个元素,原因是childNodes能获取所有的子节点,包括元素节点、文本节点等(而文本节点包括空格换行和文字,这里li后面有换行,所以5个换行+4个li就是9个元素)

        //测试
        console.log(ul.childNodes[0].nodeType); //文本节点的节点类型为3
        console.log(ul.childNodes[1].nodeType); //元素节点的节点类型为1

所以呢如果只想获得里面的元素节点,则需要专门处理。所以我们一般不提倡使用childNodes

    专门处理: 
    for(let i = 0; i < ul.childNodes.length; i++) {
         if(ul.childNodes[i].nodeType == 1){...其他操作}
    }

(2) 父节点.children(非标准)

这是一个只读属性,返回所有的子元素节点。只返回子元素节点,其他的不返回 虽然这是一个非标准,但得到了各个浏览器的支持,可以随便使用

    console.log(ul.children); //HTMLcollection(4) 是一个伪数组

(3) 父节点.firstChild、父节点.lastChild

(3)(4)见如下结构

    <ol>
        <li>我是li1</li>
        <li>我是li2</li>
        <li>我是li3</li>
        <li>我是li4</li>
    </ol>
    var ol = document.querySelector('ol');
    console.log(ol.firstChild); //#text 这是获取到了换行符,是文本类型的节点
    console.log(ol.lastChild); //#text

firstChild、lastChild两个属性分别返回第一个子节点和最后一个子节点,找不到则返回null。与childNodes一样包含的是所有节点。

(4) 父节点.firstElementChild、父节点.lastElementChild

还是刚刚(3)中的结构

        console.log(ol.firstElementChild); // <li>...</li>
        console.log(ol.lastElementChild); // <li>...</li>

这两个属性分别返回第一个子元素节点和最后一个子元素节点,找不到则返回null,但是呢这两个方法有兼容性问题,IE9以上才支持。

提出问题: 实际开发中,firstChild和lastChild包含其他节点,操作不方便,而firstElementChild和lastElementChild又有兼容性问题,那么我们如何获取第一个子元素节点或者最后一个子元素节点呢

(2)中讲了利用父节点.children获取所有子元素节点 实际开发中可以这样写

        console.log(ol.children[0]); //ol的第一个
        console.log(ol.children[ol.children.length - 1]); //ol的最后一个

兄弟节点

    <div>我是 div</div>
    <span>我是 span</span>

1.node.nextSibling
返回当前元素的下一个兄弟节点,找不到则返回null。同样获取的也是包含所有类型的节点(元素节点、属性节点、文本节点)
2.node.previousSibling
返回当前元素的上一个兄弟节点,找不到则返回null。也是包含所有类型

        console.log(div.nextSibling); //得到#text文本节点,因为div的下一个是一个换行符
        console.log(div.previousSibling); //#text,div的上一个节点还是换行^ ^

那我如果只要下一个或上一个元素节点呢

3.node.nextElementSibling
返回当前元素的下一个兄弟元素节点,找不到返回null
4.node.previousElementSibling
返回当前元素的上一个兄弟元素节点,找不到返回null

        console.log(div.nextElementSibling); // <span>我是 span</span>
        console.log(span.previousElementSibling); // <div>我是 div</div>

这两个方法同样还是有兼容性问题,ie9以上才ok

如何解决兼容性问题?

自己封装一个函数

       function getNextElementSibling(element) {
            var el = element;
            while (el = el.nextSibling) { //while中的条件是:如果往下找还有则继续
                if (el.nodeType == 1) {
                    return el;
                }
            }
            return null;
        }

创建、添加节点

见如下结构

    <!--需求:本来ul里面是没有li的,我想要动态创建一个li放进去 -->
    <ul>
        <li>123</li>
    </ul>

1.创建节点 document.createElement('tagName')
这个方法创建由tagName指定的HTML元素,因为这些元素原先不存在,是根据我们的需求动态生成的,所以也称为动态创建元素节点。

    比如
        var li = document.createElement('li');
        var li2 = document.createElement('li');

可是,只创建节点还是不够的,我们还要把创建好的节点添加进去!

2.添加节点
(1) node.appendChild(child) node是父级,child是子级
这个方法将一个节点添加到指定父节点node的子节点列表末尾。类似css中after伪元素

        var ul = document.querySelector('ul');
        ul.append(li); //把刚刚动态生成的li添加到父级ul中的子列表末尾

(2) node.insertBeforce(child,指定元素)
node.insertBeforce()方法将一个节点添加到父节点 指定子节点的前面

        ul.insertBefore(li2, ul.children[0]); //在ul的第一个元素节点前添加li2

总结:页面想要添加一个新的元素,1.创建元素 2.添加元素

删除节点

    <button>删除</button>
    <ul>
        <li>张三</li>
        <li>李四</li>
        <li>王五</li>
    </ul>

1.删除节点 node.removeChild(child)
该方法从DOM中删除一个子节点,返回删除的节点

        // 获取元素
        var ul = document.querySelector('ul');
        
        // 点击按钮依次删除
        var btn = document.querySelector('button');
        btn.onclick = function () {
            if (ul.children.length == 0) {
                this.disabled = true;
                alert('列表中已经没有元素');
            } else {
                ul.removeChild(ul.children[0]);
            }
        }

克隆节点

node.cloneNode()
返回调用该方法的节点的一个副本
如果括号中参数为空或者false,则是浅拷贝,只复制节点本身,不复制节点中的子节点。如果是true则是深拷贝

    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
    </ul>
        var ul = document.querySelector('ul');
        var li = ul.children[0].cloneNode(true); //改为false则是浅拷贝
        ul.appendChild(li);

这是克隆后的结果图(深拷贝)

image.png

浅拷贝

image.png


以上,如有错误请多多指教~