DOM属性操作与节点操作

125 阅读7分钟

一、属性操作

1-1、ECMA中的属性操作

对象.属性名 与 对象["属性名"]都是ECMA中对象的属性操作

  • 获取的是w3c规定的元素属性

如:box.className、box.id、box.tagName、 box.style

  • 要特别注意使用JS修改元素类名时是采用className来操作,因为class是保留字

1-2、DOM属性操作

DOM属性操作包括:

  • el.attributes 获取元素所有的属性集合
  • el.getAttribute("attr") 获取指定属性
  • el.setAttribute("attr","val") 为元素指定属性设置值
  • el.removeAttribute("attr") 移除指定属性
  • el.hasAttribute("attr") 判断是否有该属性
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <body>
    <div id="box" class="box" qql="DOM"></div>
    <div id="father">
      <div>div</div>
    </div>
    <script>
      let box = document.querySelector('#box');
      console.log(box.attributes);// NamedNodeMap {0: id, 1: class, 2: qql, id: id, class: class, qql: qql, length: 3}
      console.log(box.getAttribute('qql')); // DOM
      console.log(box.getAttribute('class'));  // box 注意这里不要写成className 要与属性保持一致

      box.setAttribute('qll2', '123');
      console.log(box); // <div id="box" qql="DOM" qll2="123"></div>
      box.removeAttribute('qll2');
      console.log(box.hasAttribute('qll2')); // false
    </script>
  </body>
</html>

1-3、对比ECMA属性操作与DOM属性操作

  1. ECMA属性操作
  • ECMA属性操作,操作的是对象,具体的数据存在内存中,不会在元素标签上展示(意味着可以存各种类型的数据)
  • 通过ECMA获取属性,只能获取到存储在内存中的属性
  1. DOM属性操作
  • DOM属性操作,值是存在文档中(值的类型只能是字符串,所以属性值不管设置成什么,展示在文档上时都会被转为字符串)
  • 通过DOM操作中的获取的属性,只能获取到存储在文档中的属性
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <body>
    <div id="box" class="box" qql="DOM"></div>
    <div id="father">
      <div>div</div>
    </div>
    <script>
      let box = document.querySelector('#box');
      box.index = 0; // 不会将index展示在元素标签上,操作的是对象,具体数据存储在内存中
      box.setAttribute('kkk', 0); // 会将设置的属性展示在标签上,具体数据存储在文档中
      console.log(typeof box.index); // number 通过ECMA操作属性,值可以设置成任何类型
      console.log(typeof box.getAttribute('kkk')); // string 通过DOM属性操作设置值,无论设置成什么类型 最终会被转为string 
    </script>
  </body>
</html>

!注意:只要操作了innerHTML,元素所有的子元素上,存在内存中的事件和相关属性都会丢失,如果希望元素的某些属性在操作父级的innerHTML之后还存在,那么就要把这个属性加在DOM上:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <body>
    <div id="father">
      <div>div</div>
    </div>
    <script>
      let father = document.querySelector('#father');
      let son = father.children[0];
      son.index = 1;
      son.setAttribute('kkk', '111');
      father.innerHTML = father.innerHTML;
      console.log(father.children[0].index); // undefined 只要操作了innerHTML,元素所有的子元素上,存在内存中的事件和相关属性都会丢失
      console.log(father.children[0].getAttribute('kkk')); // 111
    </script>
  </body>
</html>

1-4、自定义属性

  • 所有的自定义属性前需要加"data-"
  • 获取自定义属性:元素.dataset.属性名
  • 修改自定义属性:元素.dataset.属性名="新属性值"
  • 添加属性:元素.dataset.属性名="属性值"
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <body>
    <div id="box" data-kkk="123"></div>
    <script>
      let box = document.querySelector('#box');
      // 获取属性
      console.log(box.dataset.kkk);// 123
      // 修改属性
      box.dataset.kkk = '111';
      // 新增属性
      box.dataset.kq = 'kq';
    </script>
  </body>
</html>

二、节点操作

2-1、创建节点

document.createElement('标签名称')

  • 返回值就是创建好的element
  • 只是创建元素,要想把元素展示在页面上还需要借助别的方法:
    • parent.appendChild(node) 向父节点插入node,插入在最末尾
    • parent.insertBefore(newNode, oldNode) 向父节点插入node,插在某个node之前
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <body>
    <div id="box">
      <p>p1</p>
    </div>

    <div id="box1">
      <div id="div1"></div>
    </div>
    <div id="box2">
      <div id="div2"></div>
    </div>
    <script>
      let box = document.querySelector('#box');
      let p1 = document.querySelector('p');

      let h1 = document.createElement('h1');
      h1.innerHTML = '这是标题';
      // box.appendChild(h1);
      box.insertBefore(h1, p1); // 将h1插入在box下的p标签之前

      let box1 = document.querySelector('#box1');
      let div1 = document.querySelector('#div1');
      let div2 = document.querySelector('#div2');

      box1.insertBefore(div2, div1); // 如果插入的节点是一个已有的节点,会先将这个节点从原有位置移除,然后放入新的位置(相当于做了剪切)
    </script>
  </body>
</html>

2-2、替换节点

parent.replaceChild(newNode, oldNode)

  • 返回值时被替换的旧节点
  • 如果替换的节点是一个已经存在的节点,那么会将该节点在原始位置移除然后取替换到新位置上去(相当于做剪切操作)
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <body>
    <div id="box1">
      <div id="div1"></div>
    </div>
    <div id="box2">
      <div id="div2"></div>
    </div>
    <script>
      let box = document.querySelector('#box1');
      let div1 = document.querySelector('#div1');
      let div2 = document.querySelector('#div2');

      // 1 替换节点是新创建的节点
      let h1 = document.createElement('h1');
      h1.innerHTML = '这是一个新节点';
      //  console.log(box.replaceChild(h1, div1)); // 返回值是被替换的旧节点:div1 <div id="div1"></div>

      // 2 替换节点是已经存在的节点
      console.log(box.replaceChild(div2, div1));
    </script>
  </body>
</html>

2-3、删除节点

parent.removeChild(node)

  • 从parent中移除掉指定子节点
  • 返回值为被移除的节点
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <body>
    <div id="box1">
      <div id="div1"></div>
    </div>
    <div id="box2">
      <div id="div2"></div>
    </div>
    <script>
      let box = document.querySelector('#box1');
      let div1 = document.querySelector('#div1');

      console.log(box.removeChild(div1)); // 返回值是div1 <div id="div1"></div>

    </script>
  </body>
</html>

2-4、克隆节点

node.cloneNode(deep)

  • deep:默认时false(不传递时为false)
  • deep: true 克隆元素本身及属性,以及元素的内容和后代
  • deep: false 只克隆元素本身及属性

!注意,该方法不会将节点上的事件克隆进去

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
      #box1 {
        width: 100px;
        height: 100px;
        background-color: orange;
        border: 2px solid darkcyan;
      }
    </style>
  </head>
  <body>
    <div id="box">
      <div id="box1">
        <div>这是一个div</div>
      </div>
    </div>
    <script>
      let box = document.querySelector('#box');
      let box1 = document.querySelector('#box1');
      box1.onclick = function() {
        console.log('这是一个点击事件');
      }
      //  console.log(box1.cloneNode());
      // box.appendChild(box1.cloneNode()); // 不会将点击事件克隆进去,也不会将box1里面的子节点克隆进去
      box.appendChild(box1.cloneNode(true)); // 也不会将点击事件克隆进去,但会将box1里面子节点克隆进去

    </script>
  </body>
</html>

2-5、offset相关

  1. offsetWidth & offsetHeight
  • 元素本身的可视宽高width(height)+ padding + border
  1. offsetLeft & offsetTop
  • 元素距离定位父级左上角的距离
  • 注意,如果定位后,又为元素添加了margin,那么也要把margin计算进去
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
      #box {
        margin: 50px auto;
        position: relative;
        width: 500px;
        height: 500px;
        border: 1px solid salmon;
      }
      #div {
        position: absolute;
        left: 100px;
        top: 100px;
        width: 100px;
        height: 100px;
        padding: 50px;
        margin: 50px 0 0 -100px;
        border: 5px solid seashell;
        background-color: seagreen;
      }
    </style>
  </head>
  <body>
    <div id="box">
      <div id="div"></div>
    </div>
    <script>
      {
        let div = document.querySelector('#div');
        // width(height) + padding : 100 + 50*2 + 5*2 = 210
        console.log(div.offsetWidth, div.offsetHeight);
        // left: 100 + (-100) = 0,top:100 + 50 = 150
        console.log(div.offsetLeft, div.offsetTop);// 0 150
      }
    </script>
  </body>
</html>

2-6、client相关

  1. clientWidth & clientHeight
  • 元素本身的width(height)+ padding
  1. clientLeft & clientTop
  • 元素本身的左边框与上边框的宽度
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
      #box {
        margin: 50px auto;
        position: relative;
        width: 500px;
        height: 500px;
        border: 1px solid salmon;
      }
      #div {
        position: absolute;
        left: 100px;
        top: 100px;
        width: 100px;
        height: 100px;
        padding: 50px;
        margin: 50px 0 0 -100px;
        border: 5px solid seashell;
        background-color: seagreen;
      }
    </style>
  </head>
  <body>
    <div id="box">
      <div id="div"></div>
    </div>
    <script>
      {
        let div = document.querySelector('#div');
        console.log(div.clientWidth, div.clientHeight); // width(height) + padding : 100 + 50*2
        console.log(div.clientLeft, div.clientTop); // 5 5
      }
    </script>
  </body>
</html>

2-7、scroll相关

  1. scrollHeight
  • 元素的内容高度,如果内容高度超出了元素高度,scrollHeight就是内容高度,否则是元素本身高度
  1. scrollWidth
  • 元素的内容宽度,如果内容没有超出本身宽度,那么就是自身宽度
  • 要注意的是,如果有滚动条,滚动条自身会占有一定的宽度(结果会减去滚动条占有的宽度)
  1. scrollTop
  • 上下滚动条的位置(滚动条距离顶部的距离)
  1. scrollLeft
  • 左右滚动条的位置(滚动条距离左侧的距离)
  1. 元素.onscroll = function() {}
  • 滚动条滚动时触发的事件
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
      #box {
        margin: 50px auto;
        width: 300px;
        height: 300px;
        border: 1px solid salmon;
        overflow: auto;
      }
      #div {
        width: 100px;
        padding: 50px;
        background-color: seagreen;
      }
    </style>
  </head>
  <body>
    <div id="box">
      <div id="div">我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
        我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
        我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
        我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
        我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
        我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
        我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
        我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
        我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
        我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
        我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
        我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
        我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
        我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
        我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
        我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
        我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
        我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
        我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
        我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
        我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
        我是内容我是内容我是内容我是内容我是内容我是内容我是内容
      </div>
    </div>
    <script>
      {
        let box = document.querySelector('#box');
        console.log(box.scrollHeight); // (子元素div中内容高度 + 上下padding)

        console.log(box.scrollWidth); //  (300的宽度 - 滚动条占有的宽度)

        // setTimeout(()=> {
        //   console.log(box.scrollTop);
        // }, 2000)

        box.onscroll = function() {
          console.log(box.scrollTop);
        }
      }
    </script>
  </body>
</html>

!注意,offset、client、scroll相关获取的内容是不带单位的

三、文档碎片

在批量追加元素时,直接操作dom性能比较差,每添加一次就会造成页面重新渲染一次。

可采用文档碎片来提高效率:

document.createDocumentFragment(): 创建文档碎片

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
      #box div {
        float: left;
        width: 100px;
        height: 100px;
        background-color: darkorange;
        margin: 10px;
      }
    </style>
  </head>
  <body>
    <div id="box">
    </div>
    <script>

      {
        let box = document.querySelector('#box');

        // let inner = '';
        // console.time(1);
        // for(let i = 0; i < 1000; i++) {
        //   // box.innerHTML += `<div>${i}</div>`; // 性能比较差,每添加一次就会造成页面重新渲染一次
        //   inner += `<div>${i}</div>`;
        // }
        // box.innerHTML = inner; // 先整体拼接 最后往innerHTML中追加
        // console.timeEnd(1);

        console.time(1);
        let fragment = document.createDocumentFragment(); // 先创建一个文档碎片(相当于一个容器)
        for(let i=0;i<1000;i++) {
          let div = document.createElement('div');
          div.innerHTML = i;
          fragment.appendChild(div); // 首先将创建的节点放入到文档碎片中
        }
        box.appendChild(fragment); // 最后把文档碎片添加到box中
        console.timeEnd(1);
      }
    </script>
  </body>
</html>