# JavaScript教程 - 16 DOM

DOM 继续!

# 16.10 节点操作

上面介绍如何查找元素,以及修改元素的文本和属性。

下面来介绍如何添加、删除、替换节点,实现页面的更新。

# 1 添加节点

上面介绍了如何获取节点和修改节点的属性,下面来学习如何创建节点并添加到页面上,以及删除节点。

举个栗子:

下面在点击按钮的时候,动态的向 <ul> 列表中添加 <li> 元素。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>For技术栈</title>

    <script>
      window.onload = function () {
        // 首先获取到ul,然后操作在ul中添加元素
        const parent = document.getElementById('course-wrapper');
        // 获取按钮
        const addButton = document.getElementById('add-button');

        // 给按钮添加点击事件
        addButton.onclick = function() {

          // 创建<li>
          const li = document.createElement('li');
          // 对li元素进行设置
          li.textContent = 'JavaScript教程';

          // appendChild给节点添加子节点
          parent.appendChild(li);
        };
      };
    </script>
  </head>
  <body>
    <button id="add-button">添加</button>
    <ul id="course-wrapper">
      <li>HTML5教程</li>
      <li>CSS3教程</li>
    </ul>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
  • 首先使用 document.createElement('li') 来创建 <li> 元素;
  • 然后使用 appendChild() 方法,给节点的最后添加子元素。

显示如下:


上面在添加节点的时候,是添加到父节点的末尾,还可以指定一个兄弟节点,将节点插入到兄弟节点之前:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>For技术栈</title>

    <script>
      window.onload = function () {
        // 首先获取到ul,然后操作在ul中添加元素
        const parent = document.getElementById('course-wrapper');
  
        const cssCourse = document.getElementById('css-li');
        // 获取按钮
        const addButton = document.getElementById('add-button');

        // 给按钮添加点击事件
        addButton.onclick = function() {

          // 创建<li>
          const li = document.createElement('li');
          // 对li元素进行设置
          li.textContent = 'JavaScript教程';

          // 将<li>插入到cssCourse节点之前
          parent.insertBefore(li, cssCourse);
        };
      };
    </script>
  </head>
  <body>
    <button id="add-button">添加</button>
    <ul id="course-wrapper">
      <li>HTML5教程</li>
      <li id="css-li">CSS3教程</li>
    </ul>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
  • 在上面的代码中,使用 parent.insertBefore(li, cssCourse);<li> 插入到 cssCourse 节点之前。

还有一个 insertAdjacentElement() 方法,它可以将元素添加到父元素的前面、后面、父元素中第一个位置和父元素最后的位置。

举个栗子:

let target = document.getElementById("target");
let newDiv = document.createElement("div");
newDiv.textContent = "新的元素";

target.insertAdjacentElement("beforebegin", newDiv);  // 在 target 前面,和target同级
target.insertAdjacentElement("afterend", newDiv);  // 在 target 后面,和target同级

target.insertAdjacentElement("afterbegin", newDiv);  // 作为target的第一个子元素
target.insertAdjacentElement("beforeend", newDiv);  // 作为target的最后一个子元素
1
2
3
4
5
6
7
8
9

上面在添加节点的时候,首先创建节点,并设置节点的属性,然后将节点添加到页面中,这样操作稍微有一点麻烦。我们还可以直接使用 HTML 字符串进行添加。

举个栗子:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>For技术栈</title>

    <script>
      window.onload = function () {
        // 首先获取到ul,然后操作在ul中添加元素
        const parent = document.getElementById('course-wrapper');
  
        // 获取按钮
        const addButton = document.getElementById('add-button');

        // 给按钮添加点击事件
        addButton.onclick = function() {
          // 直接插入
          parent.insertAdjacentHTML("beforeend", "<li>我插进来了</li>");
        };
      };
    </script>
  </head>
  <body>
    <button id="add-button">添加</button>
    <ul id="course-wrapper">
      <li>HTML5教程</li>
      <li id="css-li">CSS3教程</li>
    </ul>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  • 使用 insertAdjacentHTML() 可以直接通过字符串来插入节点,同时可以使用 beforebegin/afterend/afterbegin/beforeend 指定插入的位置。

# 2 删除节点

删除节点就比较简单了,直接使用 元素.remove() 来删除节点即可。

举个栗子:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>For技术栈</title>

    <script>
      window.onload = function () {
        // 获取删除按钮
        const deleteButton = document.getElementById('delete-button');

        // 首先获取到ul,然后操作在ul中添加元素
        const cssCourse = document.getElementById('css-li');

        // 给按钮添加点击事件
        deleteButton.onclick = function() {

          let judge = confirm('确定删除吗?');

          // 确认是否需要删除
          if (judge) {
            // 直接删除
            cssCourse.remove();
          }
          
        };
      };
    </script>
  </head>
  <body>
    <button id="delete-button">删除</button>
    <ul id="course-wrapper">
      <li>HTML5教程</li>
      <li id="css-li">CSS3教程</li>
      <li>JavaScript教程</li>
    </ul>
  </body>
</html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
  • 点击删除按钮后,就删除了 cssCourse
  • 在上面的代码中,使用 confirm() 来弹出一个提示框,让用户确认是否要删除,点击确认 confirm() 会返回 true。

显式如下:

也可以通过父节点来删除,使用 parent.removeChild(child) ,也要获取子节点,那还不如直接使用 元素.remove()

# 3 替换节点

方法 parent.replaceChild(newNode, oldNode) 可以用来把 oldNode 从其父节点中移除,并用 newNode 替换它。

举个栗子:

<div id="container">
  <p id="old">原来的段落</p>
</div>
1
2
3

使用新的元素替换上面的 <p> 元素,JS代码如下:

let container = document.getElementById("container");
let old = document.getElementById("old");

let newP = document.createElement("p");
newP.textContent = "这是新的段落";

container.replaceChild(newP, old);
1
2
3
4
5
6
7

# 4 复制节点

有时候创建节点有点麻烦,我们也可以复制一个节点,然后再进行操作。

举个栗子:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>For技术栈</title>

    <script>
      window.onload = function () {
        // 获取复制按钮
        const copyButton = document.getElementById('copy-button');
        const target = document.getElementById('css-li');

        copyButton.onclick = function() {
          // 克隆节点
          const cloneNode = target.cloneNode();
          cloneNode.id = 'new-id';

          // 添加到target后面
          target.insertAdjacentElement('afterend', cloneNode);
        };
      };
    </script>
  </head>
  <body>
    <button id="copy-button">复制</button>
    <ul id="course-wrapper">
      <li>HTML5教程</li>
      <li id="css-li">CSS3教程</li>
    </ul>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
  • 在上面的代码中,通过 cloneNode() 来复制节点,注意,需要修改 id,否则 id 在页面会重复。

上面的代码运行后,点击复制按钮,会发现复制的节点没有内容,如下:

这是为什么呢?

这是因为在使用 cloneNode() 复制节点的时候,只会复制当前节点,不会复制节点中的子节点,所以文本节点不会复制。

我们可以给 cloneNode() 传递 true 参数,这样会将子节点也复制:

const cloneNode = target.cloneNode(true);
1

这样就可以了。

# 16.11 样式

下面来讲解使用 JavaScript 来操作样式。

# 1 修改样式

修改样式非常的简单,使用 元素.style.样式名称 = 样式值 来修改就可以了。

举个栗子:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>For技术栈</title>

    <style>
      #div1 {
        width: 100px;
        height: 100px;
        background-color: lightblue;
      }
    </style>
  </head>
  <body>
    <button id="button1">修改样式</button>
    <div id="div1"></div>

    <script>
      // 获取按钮
      const button1 = document.getElementById('button1');
      const div1 = document.getElementById('div1');

      button1.onclick = function() {
        // 修改样式
        div1.style.width = "200px";
        div1.style.height = "200px";
        div1.style.backgroundColor = "pink"
      };
    </script>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
  • 需要注意,通过 元素.style.样式名称 来修改样式的时候,修改的是元素的内联样式。
  • 如果css样式名称中包含 - ,则样式名称使用驼峰规则,像 background-color 变成 backgroundColor

# 2 获取样式

同样,通过 元素.style.样式名称 也可以获取样式,但是获取的也是内联样式。

如下:

// 获取按钮
const button1 = document.getElementById('button');
const div1 = document.getElementById('div1');

button1.onclick = function() {
  console.log(div1.style.width);  // 什么也没用
};
1
2
3
4
5
6
7

但是如果没有内联样式,那么什么也获取不到。其实如果内联样式不生效(例如样式表使用 !important),那么获取内联样式也没有意义。

所以获取当前标签生效的样式,才有意义,我们可以使用 getComputedStyle() 方法来获取当前元素生效的样式。

举个栗子:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>For技术栈</title>

    <style>
      #div1 {
        height: 100px;
        background-color: lightblue;
      }
    </style>
  </head>
  <body>
    <button id="button1">获取样式</button>
    <div id="div1"></div>

    <script>
      // 获取按钮
      const button1 = document.getElementById('button1');
      const div1 = document.getElementById('div1');

      button1.onclick = function() {

        // 获取div1生效的样式
        const div1Style = getComputedStyle(div1);
        console.log(div1Style.width);  // 794px
        console.log(div1Style.height);  // 100px
        console.log(div1Style.backgroundColor);  // rgb(173, 216, 230)
        console.log(div1Style.left);  // auto
        
      };
    </script>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
  • 通过 getComputedStyle(元素对象) 可以返回一个对象,其中包含了元素生效的所有样式,然后就可以获取各个样式;
  • 需要注意,获取到的样式是字符串类型的,例如长宽,带有 px ,所以如果要进行运算,需要先转换为数字,例如使用 parseInt() 转换。
  • 上面的 div 是没有设置宽度的,理论上宽度是 auto,但是获取的时候能获取到具体的宽度值,但是 leftauto,获取的时候只能获取到 auto,所以获取样式的时候需要样式的值。

如果要获取元素的伪元素的样式,可以通过如下方式获取:

const beforeStyle = getComputedStyle(div1, "::before");
1

这个了解一下,很少用到。

# 3 其他获取样式的方式

还有几种可以获取样式的方式,介绍一下。

先看代码:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>For技术栈</title>

  <style>
    #div1 {
      width: 100px;;
      height: 100px;
      padding: 30px;
      border: 10px red solid;
      margin: 30px;
      background-color: lightblue;
      overflow: scroll;
    }

    #div2 {
      width: 50px;
      height: 400px;
      background-color: pink;
    }
  </style>
</head>

<body>
  <button id="button1">获取样式</button>
  <div id="div1">
    <div id="div2"></div>
  </div>

  <script>
    // 获取按钮
    const button1 = document.getElementById('button1');
    const div1 = document.getElementById('div1');

    button1.onclick = function () {
      console.log(div1.clientWidth);  // 160,获取元素的可见宽度,包含内容区100和padding30*2
      console.log(div1.clientHeight);  // 160
      console.log(div1.offsetWidth);  // 180,获取元素的宽度,包含内容区100、padding*2和border*2
      console.log(div1.offsetHeight);  // 180
      console.log(div1.scrollWidth);  // 160,获取元素滚动区域的大小,横向没有溢出,此时和clientWidth相同
      console.log(div1.scrollHeight);  // 460,div2的高度+padding*2
    };
  </script>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
  • 在上面的代码中,就是 div1 包含 div2,设置了 两个 div 的尺寸和背景颜色,div2 的尺寸从 div1 中溢出了,设置 div1 显示滚动条。
  • 通过 clientWidthoffsetWidthscrollWidth 可以获取元素相应的尺寸。

显示如下:


还有两个属性页介绍一下,offsetTopoffsetLeft 用于获取元素相对于其最近定位祖先元素的垂直和水平偏移距离。

举个栗子:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>For技术栈</title>

  <style>
    #div1 {
      position: relative;
      width: 100px;;
      height: 100px;
      padding: 30px;
      background-color: lightblue;
    }

    #div2 {
      position: absolute;
      top: 50px;
      left: 50px;
      width: 50px;
      height: 50px;
      background-color: pink;
    }
  </style>
</head>

<body>
  <button id="button1">获取样式</button>
  <div id="div1">
    <div id="div2"></div>
  </div>

  <script>
    // 获取按钮
    const button1 = document.getElementById('button1');
    const div2 = document.getElementById('div2');

    button1.onclick = function () {
      console.log(div2.offsetParent);  // 获取到定位的父元素,也就是div1
      console.log(div2.offsetTop);  // 50
      console.log(div2.offsetLeft);  // 50
    };
  </script>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

显示如下:

  • 定位祖先元素是指开启了定位的祖先元素,positionrelativeabsolutefixedsticky 的祖先元素,都没有那就是body元素。

如果不设置 div2 的 top 和 left 属性,那么 div2 在 div1 中的位置是受 div1 的padding 影响的,此时 div2.offsetTop 值为30。

我们一般获取元素的宽高,都会使用上面的方法,使用 getComputedStyle() 获取宽高用的不多。

注意:上面的属性都是只读的。


还有两个属性,我们可以获取到它们的值,也可以修改它们的值,scrollTopscrollLeft 可以获取和设置滚动条的位置。

举个栗子:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>For技术栈</title>

  <style>
    #div1 {
      width: 100px;;
      height: 100px;
      padding: 30px;
      background-color: lightblue;
      overflow: scroll;
    }

    #div2 {
      width: 50px;
      height: 400px;
      background-color: pink;
    }
  </style>
</head>

<body>
  <button id="button1">获取样式</button>
  <div id="div1">
    <div id="div2"></div>
  </div>

  <script>
    // 获取按钮
    const button1 = document.getElementById('button1');
    const div1 = document.getElementById('div1');

    button1.onclick = function () {
      console.log(div1.scrollTop);  // 0, 滚动条没滚动的时候是0

      div1.scrollTop = 30;  // 设置垂直滚动到30px
    };
  </script>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
  • 可以通过 元素.scrollTop元素.scrollLeft 获取和设置垂直滚动条和水平滚动条的位置。

还可以通过 window.scrollTo(0, 100); 设置窗口滚动到垂直位置 100px 处,第一个参数是水平位置,第二个参数是垂直位置。

# 4 修改class

前面通过 style 来修改样式,但是这种方式只能一个一个属性进行修改,如果属性太多,修改起来会比较麻烦,而且 JS 代码和 CSS 样式会比较耦合,所以我们可以直接修改标签的 class 属性。

举个栗子:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>For技术栈</title>

  <style>
    .class1 {
      width: 100px;
      height: 100px;
      background-color: lightgreen;
    }

    .class2 {
      background-color: lightblue;
    }

    .class3 {
      background-color: pink;
    }

    .class4 {
      width: 200px;
      height: 200px;
    }
  </style>
</head>

<body>
  <button id="button1">添加样式</button>
  <button id="button2">移除样式</button>
  <button id="button3">切换样式</button>
  <button id="button4">替换样式</button>
  <button id="button5">检查样式</button>
  <div id="div1" class="class1"></div>

  <script>
    // 获取按钮
    const button1 = document.getElementById('button1');
    const button2 = document.getElementById('button2');
    const button3 = document.getElementById('button3');
    const button4 = document.getElementById('button4');
    const button5 = document.getElementById('button5');
    const div1 = document.getElementById('div1');

    button1.onclick = function () {
      // 给div1 添加样式
      div1.classList.add('class2'); // 添加单个类
      div1.classList.add('class2', 'class3', 'class4'); // 还可以添加多个类
    };

    button2.onclick = function () {
      // 移除样式
      div1.classList.remove('class2'); // 移除单个类
      div1.classList.remove('class1', 'class3'); // 还可以移除多个类
    };

    button3.onclick = function () {
      // 切换样式
      div1.classList.toggle('class2'); // 切换类,没有就添加,有就删除
      
      // div1.classList.toggle('active', true); // 强制添加
			// div1.classList.toggle('active', false); // 强制移除
    };
    
    button4.onclick = function () {
      // 替换样式
      div1.classList.replace('class2', 'class3'); // 将class1替换为class3,如果没有class1,不会替换
    };

    button5.onclick = function () {
      // 检查样式
      const hasClass = div1.classList.contains('class2'); // 检查是否存在class2
      console.log(hasClass);
    };
  </script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
  • 在上面的代码中,分别演示了添加、删除、切换、替换和检查是否包含某个class。需要先获取到元素的 classList 才可以操作。

在以前是通过 className 操作的,是有点麻烦:

const element = document.getElementById('myElement');
element.className = 'new-class'; // 替换所有现有类
element.className += ' additional-class'; // 追加类(注意前面的空格)
1
2
3

了解一下即可。