# JavaScript教程 - 16 DOM

前面主要讲解 JavaScript 的语法基础,现在开始将 JavaScript 在网页中的应用,如何使用 JavaScript 操作网页,更好的实现与人的交互。

# 16.1 DOM简介

首先需要介绍一个概念 DOM ,什么是 DOM?

DOM(Document Object Model文档对象模型),也就是将网页中的所有元素(元素、属性、文本、注释等)转换为对象,这样就可以通过 JavaScript 来操作了。

  • D(Document):网页文档;
  • O(Object):文档中的每个部分都是一个对象;
  • M(Model):就是指这些对象之间的结构关系和组织形式

每一个 HTML 标签、文本、注释等,都会变成 DOM 树中的一个 节点

举个栗子:

<body>
  <h1>Hello</h1>
  <p>Welcome!</p>
</body>

<!DOCTYPE html>
<html>
  <body>
    <h1>Hello</h1>
    <p>Welcome!</p>
  </body>
</html>

1
2
3
4
5
6
7
8
9
10
11
12
13

这段 HTML 会变成一棵树形结构,也就是 DOM 树,每个元素,就是树上的一个节点。

Document  (整个网页 = DOM 根对象)
 └── html(元素节点)
      └── body(元素节点)
           ├── h1(元素节点)
           │   └── "Hello"(文本节点)
           └── p(元素节点)
               └── "Welcome!"(文本节点)
1
2
3
4
5
6
7

JavaScript 就可以操作这些节点:添加、删除、修改、查找等,从而实现对网页的操作。

# 16.2 操作DOM

现在就来操作DOM,从而实现对网页的操作。

浏览器为我们提供了全局的 document 对象,它表示整个网页,我们之前就通过 document.write() 方法向网页中输出数据。

下面通过 document 对象来获取网页中的元素对象,这样就可以操作对象,从而实现对网页的操作。

举个栗子,编写网页如下:

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

    <button id="my-button">点我</button>

    <script>
      let myButton = document.getElementById('my-button');  // 根据id获取元素
      console.log(myButton.id);  // 打印元素的id:my-button
      myButton.innerText = '大官人点我';  // 修改 button 中的文字
    </script>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  • <body> 中定义了一个按钮,添加了一个 id;
  • 然后在 <script> 标签中编写代码,可以使用 document.getElementById(id) 方法,通过 id 来获取指定的元素;获取元素后,就可以使用对象的形式来操作元素了。
  • 需要注意,上面的 <script> 标签一定要写在后面,这个后面再讲。

上面的网页运行后,按钮的内容就变成了 大官人点我

所以后面对网页的操作就是先获取到元素对象,然后通过对象对网页进行操作。

其实还有一个全局对象 window 对象,表示整个浏览器窗口,document 对象其实是 window 对象的属性,还可以通过 window.document 来调用 document 对象。

# 16.3 document对象

document 对象表示的是整个网页,下面简单介绍一下 document 对象。

# 1 document原型链

下面了解一下 document 的原型链。

通过打印,查看一下 document 的原型链,如下:

// 1. document 的直接原型
console.log(document.__proto__); // HTMLDocument.prototype

// 2. 上一级原型
console.log(document.__proto__.__proto__); // Document.prototype

// 3. 再上一级原型
console.log(document.__proto__.__proto__.__proto__); // Node.prototype

// 4. 继续向上
console.log(document.__proto__.__proto__.__proto__.__proto__); // EventTarget.prototype

// 5. 最终指向 Object
console.log(document.__proto__.__proto__.__proto__.__proto__.__proto__); // Object.prototype
1
2
3
4
5
6
7
8
9
10
11
12
13
14

所以 document 是 HTMLDocument 类的实例,原型链如下:

HTMLDocument.prototype → Document.prototype → Node.prototype → EventTarget.prototype → Object.prototype → null
1

每一个层级的原型提供不同的功能:

层级 对象 说明
底层 HTMLDocument (或 Document) 提供 HTML 文档特有的方法(如 getElementById
Document 提供核心 DOM 方法(如 createElement
Node 提供节点操作(如 appendChildparentNode
EventTarget 提供事件监听(如 addEventListener
顶层 Object JavaScript 所有对象的基类

所以在查看 document 有哪些方法的时候,需要查看整个原型链提供的方法。

# 2 document常用属性

document 中的属性和方法非常多,这里先简单介绍几个。

属性 类型 描述
document.doctype DocumentType 返回文档的 DTD 声明(如 <!DOCTYPE html>
document.documentElement Element 返回 <html> 根元素
document.head Element 返回 <head> 元素
document.body Element 返回 <body> 元素(可读写)
document.title string 获取或设置页面标题(显示在浏览器标签页的文字)
document.characterSet string 返回文档的字符编码(如 "UTF-8"
document.URL string 返回完整的页面 URL(只读)
document.domain string 返回域名(可用于同源策略放宽)
document.referrer string 返回来源页面的 URL(若无来源则返回空字符串)
document.forms HTMLCollection 返回所有 <form> 元素的集合
document.images HTMLCollection 返回所有 <img> 元素的集合
document.links HTMLCollection 返回所有带 href<a><area> 元素
document.scripts HTMLCollection 返回所有 <script> 元素的集合
document.styleSheets StyleSheetList 返回所有样式表的列表

举个栗子:

通过 title 属性修改网页标题。

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

    <script>
      document.title = '牛逼网站';
    </script>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15


# 16.4 元素节点

在网页中,每个标签都是一个元素节点,我们可以通过 document 对象来获取和创建元素节点。

在上面通过 document.getElementById(id) 方法就是获取元素节点,同样,document.documentElementdocument.head 也是获取元素节点。

下面再介绍几种获取元素节点的方法,可以让我们更方便的获取元素节点。

# 1 通过类名获取

document.getElementsByClassName(class) 方法可以通过类名获取所有匹配的元素。

举个栗子:

<body>
  <button class="my-class" id="my-button">点我</button>

  <div>
      <span class="my-class"></span>
      <p class="my-class aclass"></span>
  </div>

  <script>
    let elements = document.getElementsByClassName('my-class');
    console.log(elements.length);  // 3
    console.log(elements);
    console.log(elements[0]);  // <button class="my-class" id="my-button">点我</button>
  </script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  • 首先在元素上定义 class 属性,然后就可以通过 getElementsByClassName() 方法获取到所有指定 class 的元素。
  • 返回的结果是一个类数组对象 HTMLCollection,可以使用下标来访问其中的元素,也可以遍历,但不是真正的数组,不能使用 forEach() 等高阶方法变量。

getElementsByClassName() 方法还可以同时指定多个类:

// 查找同时具有 "class1" 和 "class2" 的元素
const elements = document.getElementsByClassName("class1 class2");
1
2

另外需要注意,document.getElementsByClassName() 返回的结果是动态更新的,也就是说 DOM 中新增或删除了元素,不需要重新获取元素,结果已经是最新的了。

# 2 通过标签名获取

document.getElementsByTagName("div") 方法可以通过标签名来获取元素。

举个栗子:

<body>
  <span>abc</span>

  <div>
      <span>bcd</span>
      <p>def</p>
  </div>

  <script>
    let elements = document.getElementsByTagName('span');
    console.log(elements.length);
    console.log(elements);
  </script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • document.getElementsByTagName('span') 获取到所有的 <span> 标签,返回的结果也是一个类数组对象 HTMLCollection
  • 同样,返回结果也是动态更新的,DOM 发送变化,不需要重新获取结果。


document.getElementsByTagName() 方法支持通配符 "*",获取所有元素:

const allElements = document.getElementsByTagName("*");
1

# 3 通过CSS选择器获取所有

document.querySelectorAll(css选择器) 方法支持通过 css 选择器来获取元素,支持所有 CSS 选择器(ID、类、属性、伪类等)和复杂查询(如 "div > p.highlight")。

举个栗子:

// 根据id获取
let elements = document.querySelectorAll('#my-id');

// 根据标签获取
let elements = document.querySelectorAll('span');

// 根据类名获取
let elements = document.querySelectorAll('.my-class');

// 组合使用
let elements = document.querySelectorAll('div > p.highlight');
1
2
3
4
5
6
7
8
9
10
11
  • querySelectorAll() 返回的是类数组对象 NodeList,它虽然不是数组,但可以直接使用 forEach()
  • 注意,结果不会随 DOM 变化自动更新。

# 4 通过CSS选择器获取第一个

document.querySelector(css选择器) 方法也是通过 css 选择器获取元素,不过只是获取第一个匹配的元素

举个栗子:

<body>
  <div>
      <span>abc</span>
      <p>bcd</p>
  </div>
  <span>def</span>

  <script>
    let elements = document.querySelector('span');
    console.log(elements.innerHTML);  // abc
  </script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
  • 上面通过选择器获取第一个匹配的 span,如果获取不到,返回结果为 null

# 5 通过name属性获取

document.getElementsByName("username") 支持通过标签的 name 属性来获取元素。

一般是用来获取表单元素,因为不是表单元素我们也一般不添加 name 属性。

举个栗子:

<body>
  <form>
    <input type="radio" name="gender" value="male" /><input type="radio" name="gender" value="female" /></form>

  <script>
    const radios = document.getElementsByName("gender");
    radios.forEach((radio) => {
      console.log(radio.value);  // 输出male、female
    });
  </script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
  • getElementsByName() 返回所有匹配 name 属性的 NodeList ,也是类数组对象,但是支持 forEach()
  • 返回结果也会实时更新,

# 6 通过表单控件名获取

在表单内部的元素,可以直接通过 form.elements 访问。

举个例子:

<body>
  <form id="myForm">
    <input type="text" name="username" />
  </form>

  <script>
    const form = document.getElementById("myForm");
    const usernameInput = form.elements.username; // 通过name属性获取
  </script>
</body>
1
2
3
4
5
6
7
8
9
10
  • form.elements 返回的是一个表单内控件的集合(HTMLFormControlsCollection),然后通过标签的 name 属性获取 form 中的元素。

需要注意,上面有的方法不只是 document 对象可以调用,有的元素对象也可以调用,表示查找子元素。

总结一下:

方法名称 查询方式 返回类型 是否实时更新 调用对象 支持层级查询
getElementById ID 单个元素(Element) document ❌(只能全局)
getElementsByTagName 标签名 HTMLCollection document/元素
getElementsByClassName class 名 HTMLCollection document/元素
getElementsByName name 属性 NodeList document ❌(全局搜索)
querySelector CSS 选择器 单个元素(第一个) document/元素
querySelectorAll CSS 选择器 静态 NodeList document/元素

# 7 获取子节点

前面说了在 DOM 中,所有的元素都是节点,包括文本内容是文本节点。

所以我们在获取子节点的时候,会包含文本、换行。

举个栗子,获取一下子节点:

<body>
  <div id="parent">
    For技术栈
    <span>www.foooor.com</span>
    <!-- 这是注释节点 -->
    <p>快来学习</p>
  </div>

  <script>
    const element = document.getElementById("parent");
    const childNodes = element.childNodes;
    console.log(childNodes.length); // 7
  </script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • 在上面获取 div 的子节点,返回的结果是 7 个,算上 For技术栈 文本和注释节点也才 4 个而已,为什么是 7 个呢,这是因为 </span></p> 和注释标签后面还有一个换行符,换行符也是一个节点。

同样的还有获取第一个子节点 element.firstChild 和获取最后一个子节点 element.lastChild 的方法,但这些方法基本不会用,因为我们一般不会想获取子节点,而是想获取子元素

获取子元素,可以通过如下方式:

<body>
  <div id="parent">
    For技术栈
    <span>www.foooor.com</span>
    <p>快来学习</p>
  </div>

  <script>
    const element = document.getElementById("parent");
    const childrenElement = element.children;  // 获取子元素
    console.log(childrenElement.length);  // 2

    const firstElement = element.firstElementChild;
    console.log(firstElement);   // <span>www.foooor.com</span>

    const lastElement = element.lastElementChild;
    console.log(lastElement);  // <p>快来学习</p>
  </script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  • 通过 element.children 可以获取到所有的子元素,不包括文本、注释。

所以使用的时候别用错了属性,自己想获取节点还是元素。

# 8 获取父节点

通过 parentNode 可以获取到父节点, parentElement 可以获取到父元素。

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

    <script>
      const element = document.getElementById('my-span');
      console.log(element.parentNode);  // 获取父节点,即div
      console.log(element.parentElement);  // 获取父元素,也是div
    </script>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  • 父节点和父元素区别不大,用哪个都行。
  • body 的父节点和父元素都是 html,但是 html 的父节点是 document 节点,html 的父元素为 null

# 9 获取兄弟节点

可以使用 previousElementSibling 属性获取前一个属性节点,通过 nextElementSibling 获取到下一个属性节点。

<body>
    <h1>For技术栈</h1>
    <div id="domain">www.foooor.com</div>
    <p>快来学习</p>

  <script>
    const element = document.getElementById("domain");
    const preElement = element.previousElementSibling; // 获取前一个兄弟元素,即h1元素
    console.log(preElement); // 

    const nextElement = element.nextElementSibling;  // 获取下一个兄弟元素,即p元素
    console.log(nextElement);
  </script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14