# 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>
2
3
4
5
6
7
8
9
10
11
12
13
这段 HTML 会变成一棵树形结构,也就是 DOM 树,每个元素,就是树上的一个节点。
Document (整个网页 = DOM 根对象)
└── html(元素节点)
└── body(元素节点)
├── h1(元素节点)
│ └── "Hello"(文本节点)
└── p(元素节点)
└── "Welcome!"(文本节点)
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>
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
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
每一个层级的原型提供不同的功能:
层级 | 对象 | 说明 |
---|---|---|
底层 | HTMLDocument (或 Document ) | 提供 HTML 文档特有的方法(如 getElementById ) |
↑ | Document | 提供核心 DOM 方法(如 createElement ) |
↑ | Node | 提供节点操作(如 appendChild 、parentNode ) |
↑ | 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>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 16.4 元素节点
在网页中,每个标签都是一个元素节点,我们可以通过 document 对象来获取和创建元素节点。
在上面通过 document.getElementById(id)
方法就是获取元素节点,同样,document.documentElement
和 document.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>
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");
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>
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("*");
# 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');
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>
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>
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>
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>
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>
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>
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>
2
3
4
5
6
7
8
9
10
11
12
13
14
← 15-定时器与事件循环 16-DOM-2 →