很多人刚学前端时都会听到一句话:
JavaScript 通过 DOM 操作页面。
但继续问下去:
document.querySelector() 为什么能选到元素?这篇文章的目标,就是把这些问题一次讲清。
DOM(Document Object Model,文档对象模型),可以理解为:
浏览器把一份文档解析后,在内存里构造成一棵可被程序读取和修改的对象树。
这棵树里的每一个点,都是一个 node(节点)。页面里的标签、文本、注释,都会以节点的形式存在。
例如这段 HTML:
<!doctype html>
<html>
<body>
<h1>Hello</h1>
<p>DOM is a tree.</p>
</body>
</html>浏览器不会直接“拿字符串 HTML 去交互”,而是会把它解析成近似下面这样的结构:
Document
└── html
└── body
├── h1
│ └── "Hello"
└── p
└── "DOM is a tree."这就是常说的 DOM tree(DOM 树)。
所以要抓住第一个关键点:
HTML 更像“设计图纸”,DOM 更像“运行中的对象模型”。JavaScript 能改页面,是因为它改的不是原始 HTML 字符串,而是这棵 DOM 树。
如果浏览器只是把 HTML 当普通文本展示,那页面就无法进行程序化交互。
例如:
这些都要求页面结构必须能被程序访问、查询、修改。
DOM 解决的就是这个问题:它把“文档”变成了“对象”。
这样 JavaScript 就可以用统一的 API 去操作页面,例如:
const title = document.querySelector('h1')
title.textContent = 'Hello DOM'这里发生的事情并不是“替换 HTML 文件里的字符串”,而是:
document 拿到当前文档对象h1 节点这也是为什么 DOM 是前端交互的基础层。你在 React、Vue、Svelte 里写的组件,最终也要落回到浏览器 DOM 才能显示给用户。
documentdocument 表示当前页面对应的 DOM 文档对象。
它通常是你接触 DOM 的入口:
console.log(document)
console.log(document.title)
console.log(document.body)可以把它理解成“整棵 DOM 树的根入口”。
NodeNode 是 DOM 中最基础的节点接口。任何 DOM 树里的成员,本质上都可以看作节点。
常见节点包括:
Document)Element)Text)Comment)所以 Node 是更底层、更泛化的概念。
ElementElement 指元素节点,也就是像 <div>、<p>、<button> 这样的标签对象。
比如:
const button = document.querySelector('button')
console.log(button)这里拿到的通常就是一个 Element(更具体地说,在 HTML 里常是 HTMLElement)。
很多初学者会忽略这一点:
<p>Hello DOM</p>这个 p 元素内部的 Hello DOM 不是元素,而是一个 文本节点。
也就是说,DOM 不是“只有标签的一棵树”,而是“标签、文本、注释等混合组成的一棵树”。
理解 DOM,最重要的是建立“树”的思维。
例如:
<ul id="todo-list">
<li>Learn HTML</li>
<li>Learn CSS</li>
<li>Learn JavaScript</li>
</ul>对应的 DOM 可以粗略理解为:
ul#todo-list
├── li
│ └── "Learn HTML"
├── li
│ └── "Learn CSS"
└── li
└── "Learn JavaScript"于是很多 DOM API 就很好理解了:
parentNode / parentElement:父节点childNodes:所有子节点children:所有子元素firstChild / lastChildnextSibling / previousSibling例如:
const list = document.querySelector('#todo-list')
console.log(list.children.length) // 3
console.log(list.firstElementChild.textContent) // Learn HTML当你把页面当树来理解,DOM API 本质上就是“树的查询与修改工具”。
现代前端里最常用的是 querySelector 和 querySelectorAll:
const title = document.querySelector('h1')
const items = document.querySelectorAll('li')它们的优势是:可以直接使用 CSS 选择器语法。
比如:
document.querySelector('#app')
document.querySelector('.card.active')
document.querySelector('input[type="email"]')这也是为什么 querySelector 成为最推荐的现代写法——它直观、统一,而且与 CSS 选择器共享一套思维模型。
const link = document.querySelector('a')
console.log(link.textContent)
console.log(link.href)const title = document.querySelector('h1')
title.textContent = 'DOM 学习中'const ul = document.querySelector('ul')
const li = document.createElement('li')
li.textContent = 'Learn DOM'
ul.appendChild(li)这段代码的意思是:
li 元素节点ul 的子节点末尾这就是典型的 DOM 树修改。
很多人把 DOM 理解成“查元素 + 改内容”,但其实事件系统也是 DOM API 的核心组成部分。
例如:
const button = document.querySelector('button')
button.addEventListener('click', () => {
alert('你点击了按钮')
})这里你不仅拿到了 DOM 元素,还给它绑定了交互行为。
所以从工程视角看,DOM 至少包含三层能力:
这也是为什么说 DOM 不是“一个对象”那么简单,而是一整套浏览器文档交互模型。
innerHTML、innerText、textContent 到底有什么区别这是 DOM 初学阶段最容易混淆的一组概念。
textContent表示节点及其后代节点的纯文本内容。
const p = document.querySelector('p')
p.textContent = '<strong>Hello</strong>'页面上会看到的是:
<strong>Hello</strong>也就是说,它把内容当普通文本处理,不解析为 HTML。
innerHTML表示元素内部的 HTML 结构。
p.innerHTML = '<strong>Hello</strong>'这时页面上会真正渲染出加粗的 Hello。
如果你只是想安全地设置文本内容,优先使用:
textContent如果你确实要插入一段 HTML 结构,才考虑:
innerHTML因为 innerHTML 不只是“更方便”,它还意味着:
所以一个非常实用的原则是:
能用
textContent,就不要先用innerHTML。
不对。
HTML 是文档源码,DOM 是浏览器解析后在内存里的对象模型。两者有关联,但不是一回事。
不对。
文本、注释、文档本身也都是节点。DOM 树不是“标签树”,而是“节点树”。
不对。
JavaScript 修改的是当前页面运行时的 DOM 状态,不是你磁盘里的 .html 文件。
也不对。
框架确实帮你减少了手写 DOM 操作,但事件、表单、焦点、滚动、选择器、挂载节点、浏览器 API 这些问题,最终还是落回 DOM。
不会 DOM,很多前端问题你只能“背框架 API”,但看不清底层发生了什么。
这也是过度简化。
真正的问题通常不是“碰了 DOM 就慢”,而是:
所以更准确的说法应该是:
不合理的 DOM 操作会影响性能,而不是 DOM 本身天然不能碰。
这句话有一半对,一半不对。
对的部分在于:DOM 变更往往会影响浏览器后续工作,比如样式计算、布局(layout)、绘制(paint)。
尤其当页面节点很多时,浏览器处理这些步骤的成本会上升。
例如你反复执行这种逻辑:
for (let i = 0; i < 1000; i++) {
element.style.width = `${i}px`
console.log(element.offsetWidth)
}这里的问题不是“用了 DOM API”,而是你在写布局属性后又立刻读取布局结果,容易导致浏览器反复进行布局计算。这类模式常被称为 layout thrashing。
更合理的思路通常是:
对于初学者来说,只要先记住一句话:
DOM 操作不是不能做,而是要避免无意义、高频、打断浏览器优化节奏的操作。
如果你是前端初学者到中级开发者,至少要掌握这些:
Document、Node、Element 的层级关系querySelector / querySelectorAlltextContent / innerHTMLcreateElementappendChild / removeChildclassListsetAttribute / 直接属性读写addEventListenerinnerHTML 要谨慎,尤其面对用户输入如果这些概念你已经顺了,后面学 React、Vue、事件冒泡、表单、Shadow DOM、渲染优化时,就会轻松很多。
如果只用一句话概括 DOM,那就是:
DOM 是浏览器把文档表示为对象树的一种模型,让 JavaScript 能以编程方式读取、修改和监听页面。
你可以把它理解成“页面在浏览器里的可编程形态”。
前端开发里,HTML 负责描述内容,CSS 负责描述样式,而 DOM 则是让页面“可被程序操作”的那一层桥梁。
理解了 DOM,你才真正开始理解浏览器中的页面到底是什么。
如果你准备继续往下学,下一步建议接这几个主题:
这些内容会让你把“会用 DOM”推进到“理解浏览器如何工作”。