网站
引入
JS 需要操作网页内容和结构。但 JS 不能直接读 HTML 源码,浏览器提供了一套接口,把 HTML 转成 JS 可以操作的对象结构,这就是 DOM。
DOM 把网页转成一棵”树”,JS 通过这棵树可以访问和修改页面上的任何内容。
正文
定义
定义
DOM(Document Object Model)是浏览器把 HTML 文档转换成的对象模型。整个页面被表示为一棵树,每个标签、文本、注释都是树上的一个节点。
简单说:DOM 是 JS 操作页面的桥梁。没有 DOM,JS 就接触不到 HTML。
DOM 树
浏览器读取 HTML 后,会把标签按照嵌套关系组织成一棵树。
<!DOCTYPE html>
<html>
<head>
<title>页面标题</title>
</head>
<body>
<h1>标题</h1>
<p>一段文字</p>
</body>
</html>转换后的 DOM 树结构大致如下:
document
└── html
├── head
│ └── title
│ └── "页面标题"
└── body
├── h1
│ └── "标题"
└── p
└── "一段文字"
document 是整棵树的根节点,html 是它的子节点,head 和 body 是 html 的子节点,依此类推。
- 外层标签是内层标签的父节点
- 内层标签是外层标签的子节点
- 同一层级的标签互为兄弟节点
节点类型
DOM 树上的每个组成部分都叫节点,但节点的类型不同。
常见的三种节点:
| 节点类型 | 说明 | 示例 |
|---|---|---|
| 元素节点 | HTML 标签本身 | <h1>、<p>、<div> |
| 文本节点 | 标签里的文字内容 | "标题"、"一段文字" |
| 注释节点 | HTML 注释 | <!-- 这是一个注释 --> |
元素节点是最常操作的节点类型。
<p>一段文字</p>这里 <p> 是元素节点,"一段文字" 是 <p> 里面的文本节点。
属性(例如 class、id、src)不属于独立的子节点,它们挂在元素节点上。
nodeType
nodeType 用来判断一个节点属于什么类型,返回一个数字。处理 firstChild、childNodes 这类可能包含文本节点的结果时,它很有用。
<p id="text">一段文字</p>const text = document.querySelector("#text");
console.log(text.nodeType); // 1,元素节点
console.log(text.firstChild.nodeType); // 3,文本节点常见的 nodeType 值:
| 值 | 常量 | 节点类型 |
|---|---|---|
1 | Node.ELEMENT_NODE | 元素节点 |
3 | Node.TEXT_NODE | 文本节点 |
8 | Node.COMMENT_NODE | 注释节点 |
9 | Node.DOCUMENT_NODE | document 节点 |
判断时优先使用 Node.ELEMENT_NODE 这类常量,比直接记数字更容易理解。
if (text.nodeType === Node.ELEMENT_NODE) {
console.log("这是元素节点");
}子节点访问
firstChild 和 lastChild 分别获取一个节点的第一个、最后一个子节点。它们属于 Node 接口,所以元素节点、文本节点和 document 等节点都可能使用。
<ul id="list"><li>HTML</li><li>CSS</li></ul>const list = document.querySelector("#list");
console.log(list.firstChild); // 第一个 <li>
console.log(list.lastChild); // 最后一个 <li>firstChild 和 lastChild 会把文本节点也算进去。HTML 换行和缩进产生的空白,通常也会成为文本节点。
<ul id="list">
<li>HTML</li>
<li>CSS</li>
</ul>const list = document.querySelector("#list");
console.log(list.firstChild.nodeType); // 3,换行产生的文本节点
console.log(list.firstElementChild); // 第一个 <li>
console.log(list.lastElementChild); // 最后一个 <li>只需要处理 HTML 标签时,优先使用 firstElementChild、lastElementChild 或 children,可以避开空白文本节点。
元素对象
HTML 里的每个标签在 DOM 中都是一个元素对象。
JS 获取到这个元素对象后,就可以读取或修改它的信息。
<p class="text">旧内容</p>const text = document.querySelector(".text");
console.log(text.tagName);
console.log(text.textContent);
console.log(text.className);输出:
P
旧内容
text
tagName:元素的标签名(大写)textContent:元素的文本内容className:元素的class属性值
元素对象上还有很多属性和方法,例如 style、children、innerHTML、setAttribute()、addEventListener() 等。
document 对象
document 是 DOM 操作的入口,代表整个 HTML 文档。
console.log(document);document 对象本身也是一个节点,它是整棵 DOM 树的根。
常用入口属性:
| 属性/方法 | 作用 |
|---|---|
document.body | 获取 <body> 元素 |
document.head | 获取 <head> 元素 |
document.title | 获取或设置页面标题 |
document.querySelector() | 查找第一个匹配元素 |
document.querySelectorAll() | 查找所有匹配元素 |
document.createElement() | 创建一个新元素 |
document.createTextNode() | 创建一个新文本节点 |
console.log(document.title);
document.title = "新标题";document 是一切 DOM 操作的起点。
DOM 和 HTML 的关系
HTML 是源码,DOM 是浏览器解析后生成的对象结构。
JS 修改 DOM 时,浏览器会实时更新页面显示。
<p class="text">旧内容</p>const text = document.querySelector(".text");
text.textContent = "新内容";JS 修改了 textContent 后,页面上的文字也会立刻变成”新内容”。
浏览器不会修改原始的 HTML 文件,只改变内存中的 DOM 和页面显示。
常见写法
| 写法 | 作用 |
|---|---|
document.querySelector(".text") | 获取第一个 class 为 text 的元素 |
document.querySelectorAll("p") | 获取所有 <p> 元素 |
element.textContent | 读取或修改元素文本 |
element.style.color = "red" | 修改元素的样式 |
document.createElement("div") | 创建一个 <div> 元素 |
parent.appendChild(child) | 向父元素添加一个子元素 |
element.addEventListener("click", fn) | 给元素绑定点击事件 |
element.remove() | 从页面中移除元素 |
node.nodeType | 获取节点类型 |
node.firstChild | 获取第一个子节点,可能是文本节点 |
node.lastChild | 获取最后一个子节点,可能是文本节点 |
element.firstElementChild | 获取第一个元素子节点 |
例子
获取元素并修改内容
<h1 id="title">旧标题</h1>const title = document.querySelector("#title");
title.textContent = "新标题";先通过 document.querySelector() 获取 <h1> 元素对象,再修改它的 textContent。
读取输入框内容
<input id="userName" type="text">
<button id="btn">提交</button>const userName = document.querySelector("#userName");
const btn = document.querySelector("#btn");
btn.addEventListener("click", function () {
console.log(userName.value);
});点击按钮时,通过 value 取出输入框当前的内容。
遍历多个元素
<li class="item">HTML</li>
<li class="item">CSS</li>
<li class="item">JS</li>const items = document.querySelectorAll(".item");
items.forEach(function (item) {
console.log(item.textContent);
});获取所有 .item 元素后,用 forEach() 遍历,依次输出每个元素的文本。
特点
- DOM 是 HTML 文档的对象表示,是 JS 操作页面的桥梁
- 每个 HTML 标签在 DOM 中都是一个元素对象
- DOM 以树形结构组织,有父子关系和兄弟关系
document是 DOM 树的根节点,一切操作从这里开始- 元素节点是最常操作的节点类型
- 文本内容保存在文本节点中,标签属性挂在元素节点上
nodeType可以用来判断节点类型firstChild和lastChild会包含换行、空格产生的文本节点- 只关心标签时,使用
firstElementChild、lastElementChild或children更合适 - JS 修改 DOM 后,浏览器会实时更新页面显示
- 修改 DOM 不会改原始 HTML 文件,只改变内存中的结构和页面显示
- 大部分 DOM 操作需要先获取元素,再对元素进行操作
注意事项
- JS 操作 DOM 前,要确保目标元素已经被浏览器解析出来
- 如果 JS 写在
<head>里且没有defer,执行时<body>可能还没加载 - 推荐把
<script>放在<body>结束前,或使用defer属性 - 获取不到元素时,
querySelector()返回null,继续操作会报错 - 频繁操作 DOM 会影响页面性能,尤其是循环中反复修改 DOM 时
理解
可以把 DOM 理解为一棵”标签树”。
浏览器把 HTML 里的标签按嵌套关系搭成树形结构,每个标签变成一个”节点对象”。
JS 通过这些节点对象可以找到标签、读取内容、修改样式、添加新标签等。
我理解 DOM 的重点是:浏览器把 HTML 变成一棵对象树,JS 通过这棵树操作页面。
document 就是这棵树的入口,拿到了它,就等于拿到了整个页面的操作权。
引出
理解了 DOM 之后,就可以学习 JS 获取元素,知道怎么找到页面上的具体元素。
后续可以结合 JS 修改元素、JS 创建和删除元素 和 JS 事件,实现完整的页面交互功能。
当页面内容会被其他代码动态增删时,怎样自动感知这些变化?可以了解 JS MutationObserver。