Article
一、DOM 基础概念
1.1 什么是 DOM
| 概念名称 | 说明 | 注意事项 |
|---|---|---|
| DOM(Document Object Model) | 文档对象模型,是 HTML 和 XML 文档的编程接口。它将整个文档表示为一个由节点和对象组成的树结构,允许程序或脚本动态访问和更新文档的内容、结构和样式。 | DOM 不是 JavaScript 的一部分,而是由浏览器实现的 Web API;不同浏览器对 DOM 的实现可能略有差异。 |
| DOM 与 HTML 的关系 | HTML 是静态标记语言,而 DOM 是浏览器在加载 HTML 后构建的动态内存中的对象模型。JavaScript 通过操作 DOM 来改变页面内容。 | 修改 DOM 会触发页面重排(reflow)和重绘(repaint),影响性能,应尽量减少频繁操作。 |
| DOM 标准 | 由 W3C 制定,定义了节点、方法、属性等规范。现代浏览器遵循 DOM Level 4 或更高标准。 | 开发时建议查阅 MDN Web Docs 获取最新兼容性信息。 |
1.2 DOM 树结构与节点类型
| 节点类型 | 说明 | 注意事项 |
|---|---|---|
| Document 节点 | 整个文档的根节点,代表 HTML 文档本身。可通过 document 全局变量访问。 | 只有一个 Document 节点,是所有其他节点的祖先。 |
| Element 节点 | 表示 HTML 元素(如 <div>、<p>)。是最常用的节点类型。 | 所有 Element 节点都继承自 HTMLElement 接口,具有 id、className、style 等属性。 |
| Text 节点 | 表示元素内的文本内容。例如 <p>Hello</p> 中的 “Hello” 是一个 Text 节点。 | 空白字符(如换行、缩进)也会生成 Text 节点,遍历时需注意过滤。 |
| Comment 节点 | 表示 HTML 注释(<!-- comment -->)。 | 通常用于调试或标记,不会渲染到页面上。 |
| Attribute 节点(已弃用) | 在早期 DOM 中,属性被视为节点,但现代 DOM 中属性通过 Element 的方法(如 getAttribute)访问,不再作为独立节点。 | 不建议使用 attributes 集合作为主要操作方式,优先使用属性方法。 |
| DocumentFragment 节点 | 轻量级文档容器,用于临时存放多个节点,避免多次重排。 | 插入后自身不保留,只插入其子节点,常用于批量 DOM 操作优化。 |
1.3 节点关系(父子兄弟)
| 关系属性/方法 | 语法 | 用途 | 注意事项 |
|---|---|---|---|
| parentNode | node.parentNode | 返回当前节点的父节点。 | 若节点为 Document,则返回 null。 |
| childNodes | node.childNodes | 返回包含所有子节点(包括 Text、Comment 等)的 NodeList。 | NodeList 是动态集合,DOM 变化时自动更新;包含非元素节点,需用 nodeType 过滤。 |
| firstChild / lastChild | node.firstChildnode.lastChild | 分别返回第一个和最后一个子节点。 | 可能返回 Text 节点(如空白),若只想获取元素节点,应使用 firstElementChild / lastElementChild。 |
| nextSibling / previousSibling | node.nextSiblingnode.previousSibling | 返回下一个或上一个同级节点。 | 同样可能指向非元素节点,建议使用 nextElementSibling / previousElementSibling 获取元素兄弟。 |
| children | node.children | 返回仅包含 Element 子节点的 HTMLCollection。 | 比 childNodes 更常用,因为只包含元素节点。 |
| parentElement | node.parentElement | 返回当前节点的父元素节点(仅 Element 类型)。 | 对于 Document 或 DocumentFragment 节点,返回 null。 |
二、元素选择与查询
2.1 通过 ID 获取元素
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| getElementById | document.getElementById(id) | 根据唯一 ID 获取单个元素节点。 | const el = document.getElementById('myDiv'); | ID 在文档中应唯一,若重复,只返回第一个匹配项 返回 null 若未找到 不区分大小写在 HTML 中无效(HTML 规范要求 ID 区分大小写) |
2.2 通过标签名、类名获取元素
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| getElementsByTagName | document.getElementsByTagName(tagName)element.getElementsByTagName(tagName) | 根据标签名获取所有匹配的元素集合(HTMLCollection)。 | const divs = document.getElementsByTagName('div'); | 返回动态 HTMLCollection(DOM 变化时自动更新)tagName 不区分大小写(如 'DIV' 等同于 'div')可在任意 Element 上调用,仅搜索其子树 |
| getElementsByClassName | document.getElementsByClassName(classNames)element.getElementsByClassName(classNames) | 根据一个或多个类名获取元素集合(HTMLCollection)。 | const items = document.getElementsByClassName('item active'); | classNames 是空格分隔的字符串,表示同时包含这些类返回动态 HTMLCollection 不支持复杂选择器(如伪类、属性等) IE9+ 支持 |
2.3 使用 CSS 选择器查询(querySelector / querySelectorAll)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| querySelector | document.querySelector(selector)element.querySelector(selector) | 使用 CSS 选择器语法获取第一个匹配的元素。 | const firstBtn = document.querySelector('.btn.primary'); | 返回 null 若未找到 支持所有 CSS3 选择器(如 :nth-child、[attr=value]、>、+ 等)性能略低于 getElementById,但功能强大 |
| querySelectorAll | document.querySelectorAll(selector)element.querySelectorAll(selector) | 使用 CSS 选择器语法获取所有匹配的元素。 | const allInputs = document.querySelectorAll('input[type="text"]'); | 返回静态 NodeList(不会随 DOM 更新而变化) 即使只有一个元素也返回 NodeList,需用 [0] 或解构访问不支持 CSS 伪元素(如 ::before)选择器语法错误会抛出 DOMException |
三、节点内容与属性操作
3.1 innerHTML / innerText / textContent
| 属性名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| innerHTML | element.innerHTMLelement.innerHTML = htmlString | 获取或设置元素的 HTML 内容(包含标签)。 | div.innerHTML = '<p>Hello <strong>World</strong></p>'; | 设置时会解析 HTML 字符串并重建子节点 存在 XSS 风险,不可用于插入用户输入内容 获取时返回序列化的 HTML 字符串 |
| innerText | element.innerTextelement.innerText = text | 获取或设置元素的”可见”文本内容(受 CSS 影响,如 display:none 的内容不包含)。 | span.innerText = 'New visible text'; | 受样式影响(如隐藏元素的文本不会被读取) 设置时会转义 HTML 特殊字符(安全) 非标准起源(IE),但现代浏览器广泛支持 会触发重排(reflow) |
| textContent | element.textContentelement.textContent = text | 获取或设置元素的所有文本内容(包括 script/style 和隐藏元素),忽略 HTML 标签。 | code.textContent = '<script>alert(1)</script>'; // 显示为纯文本 | 不解析 HTML,完全安全 不受 CSS 影响(即使元素隐藏也会读取) 性能优于 innerText(不触发重排)推荐用于纯文本操作 |
3.2 操作元素属性(getAttribute / setAttribute / removeAttribute)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| getAttribute | element.getAttribute(name) | 获取指定属性的字符串值。 | const href = link.getAttribute('href'); | 返回字符串,即使属性是布尔值(如 disabled 返回 "disabled" 或 null)可获取自定义属性(如 data-*) |
| setAttribute | element.setAttribute(name, value) | 设置指定属性的值(自动转为字符串)。 | img.setAttribute('src', 'image.jpg'); | 若属性已存在则覆盖,否则新增 布尔属性(如 checked)需设为任意非空字符串才生效(建议用 true/false 字符串) |
| removeAttribute | element.removeAttribute(name) | 移除指定属性。 | button.removeAttribute('disabled'); | 移除后属性不再存在,hasAttribute 返回 false对布尔属性特别有效(如移除 disabled 使按钮可用) |
| hasAttribute | element.hasAttribute(name) | 检查元素是否具有指定属性。 | if (input.hasAttribute('required')) { ... } | 返回布尔值 常用于条件判断 |
3.3 操作样式(style / classList)
(1)直接操作内联样式(style 属性)
| 用法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 设置单个样式 | element.style.property = value | 设置内联 CSS 样式(驼峰命名)。 | div.style.backgroundColor = 'red';div.style.fontSize = '16px'; | 属性名需用 camelCase(如 background-color → backgroundColor)仅影响内联样式,优先级高但难以维护 获取时只返回内联样式,不反映外部 CSS 计算值 |
| 获取计算样式 | window.getComputedStyle(element) | 获取元素最终应用的所有 CSS 属性值。 | const styles = getComputedStyle(div);console.log(styles.color); | 返回只读 CSSStyleDeclaration 对象 包含继承和外部样式表计算后的值 性能开销较大,避免频繁调用 |
(2)操作 CSS 类(classList 属性)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| add | element.classList.add(className) | 添加一个或多个 CSS 类。 | btn.classList.add('active', 'highlight'); | 自动去重,重复添加无效 支持多个参数(ES6+) |
| remove | element.classList.remove(className) | 移除一个或多个 CSS 类。 | btn.classList.remove('disabled'); | 若类不存在,静默忽略 可传多个类名 |
| toggle | element.classList.toggle(className, [force]) | 切换类的存在状态(有则删,无则加)。 | menu.classList.toggle('open'); | 第二个参数 force 为布尔值:true 强制添加,false 强制移除返回操作后是否包含该类(true/false) |
| contains | element.classList.contains(className) | 检查是否包含指定类。 | if (el.classList.contains('error')) { ... } | 返回布尔值 常用于状态判断 |
| replace | element.classList.replace(oldClass, newClass) | 替换一个类为另一个。 | item.classList.replace('loading', 'loaded'); | 若 oldClass 不存在,则不添加 newClassIE 不支持(需 polyfill) |
四、创建与插入节点
4.1 创建元素节点与文本节点
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| createElement | document.createElement(tagName) | 创建一个新的 HTML 元素节点。 | const div = document.createElement('div'); | tagName 不区分大小写返回的元素尚未加入 DOM 树,需通过插入方法添加 可随后设置属性、内容等 |
| createTextNode | document.createTextNode(text) | 创建一个包含指定文本的文本节点。 | const textNode = document.createTextNode('Hello'); | text 会被转义为纯文本(安全)常用于避免 innerHTML 的 XSS 风险 可作为子节点插入到元素中 |
| createDocumentFragment | document.createDocumentFragment() | 创建一个轻量级的文档片段容器,用于临时存放多个节点。 | const frag = document.createDocumentFragment();frag.appendChild(div); | 插入到 DOM 时只插入其子节点,自身不保留 批量操作可减少重排次数,提升性能 |
4.2 插入节点(appendChild / insertBefore)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| appendChild | parent.appendChild(child) | 将子节点添加到父节点的最后一个子节点之后。 | container.appendChild(newDiv); | 若 child 已存在于 DOM 中,则会移动而非复制 返回被插入的节点 支持 DocumentFragment(插入其所有子节点) |
| insertBefore | parent.insertBefore(newNode, referenceNode) | 在指定参考节点之前插入新节点。 | list.insertBefore(newItem, list.firstChild); | referenceNode 不能为 null(否则行为未定义)若 referenceNode 为第一个子节点,则 newNode 成为新的第一个子节点若 newNode 已存在,则先移除再插入 |
| prepend (现代方法) | parent.prepend(...nodes) | 将节点或字符串插入到父节点的开头。 | header.prepend(titleNode, ' - '); | 支持多个参数(节点或字符串) 字符串自动转为 Text 节点 IE 不支持,需 polyfill 或使用 insertBefore |
| append (现代方法) | parent.append(...nodes) | 将节点或字符串插入到父节点的末尾。 | footer.append(copyrightText); | 类似 appendChild,但支持多个参数和字符串 比 appendChild 更灵活 IE 不支持 |
4.3 替换与克隆节点
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| replaceChild | parent.replaceChild(newChild, oldChild) | 用新节点替换父节点中的旧子节点。 | container.replaceChild(updatedDiv, oldDiv); | oldChild 必须是 parent 的直接子节点,否则抛出异常返回被替换的旧节点 newChild 若已存在于 DOM 中,会被先移除再插入 |
| cloneNode | node.cloneNode([deep]) | 克隆一个节点。 | const copy = original.cloneNode(true); | 参数 deep 为布尔值:true 表示深拷贝(包括所有子节点),false 仅克隆当前节点不会复制事件监听器(除非使用 addEventListener 添加且未标记 passive 等特殊情况) 克隆后的节点 ID 可能重复,需手动处理 |
| remove (现代方法) | node.remove() | 从 DOM 中移除当前节点。 | obsoleteElement.remove(); | 等效于 node.parentNode.removeChild(node)更简洁,无需访问父节点 IE 不支持,传统方式需用 removeChild |
五、删除与遍历节点
5.1 删除节点(removeChild / remove)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| removeChild | parent.removeChild(child) | 从父节点中移除指定的子节点。 | const removed = container.removeChild(item); | child 必须是 parent 的直接子节点,否则抛出 NotFoundError 返回被移除的节点 移除后该节点仍存在于内存中,可重新插入 |
| remove | node.remove() | 直接从其父节点中移除当前节点(现代方法)。 | obsoleteDiv.remove(); | 无需显式访问父节点,语法更简洁 等效于 node.parentNode?.removeChild(node)IE 不支持,在旧项目中需使用 removeChild |
5.2 遍历子节点与兄弟节点
| 遍历方式 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| childNodes | node.childNodes | 获取所有子节点(包括 Text、Comment 等)的 NodeList。 | for (let i = 0; i < el.childNodes.length; i++) { ... } | 包含非元素节点(如空白文本) 是动态集合(DOM 变化时自动更新) 遍历时建议检查 nodeType === Node.ELEMENT_NODE(值为 1)过滤 |
| children | node.children | 获取仅包含 Element 子节点的 HTMLCollection。 | Array.from(el.children).forEach(child => ...) | 只包含元素节点,更常用 动态集合 不包含 Text/Comment 节点 |
| firstChild / lastChild | node.firstChildnode.lastChild | 获取第一个或最后一个子节点(任意类型)。 | const first = list.firstChild; | 可能返回 Text 节点(如换行符) 若只想获取元素节点,应使用 firstElementChild / lastElementChild |
| firstElementChild / lastElementChild | node.firstElementChildnode.lastElementChild | 获取第一个或最后一个元素子节点。 | const firstItem = ul.firstElementChild; | 若无元素子节点,返回 null 推荐用于结构化遍历 |
| nextSibling / previousSibling | node.nextSiblingnode.previousSibling | 获取下一个或上一个同级节点(任意类型)。 | let sibling = current.nextSibling; | 可能指向空白 Text 节点 建议使用 nextElementSibling / previousElementSibling 获取元素兄弟 |
| nextElementSibling / previousElementSibling | node.nextElementSiblingnode.previousElementSibling | 获取下一个或上一个元素兄弟节点。 | while (el = el.nextElementSibling) { ... } | 忽略非元素节点,适合 DOM 结构遍历 若无对应兄弟,返回 null |
5.3 文档片段(DocumentFragment)优化批量操作
| 操作步骤 | 操作细节 | 注意事项 |
|---|---|---|
| 创建文档片段 | 使用 document.createDocumentFragment() 创建一个轻量级容器。 | DocumentFragment 不是真实 DOM 节点,不会触发重排/重绘 初始时无子节点 |
| 向片段添加节点 | 使用 appendChild、append 等方法将多个节点加入片段。 | 所有操作在内存中进行,性能高 可混合添加元素、文本等节点 |
| 将片段插入 DOM | 将整个 DocumentFragment 插入目标父节点(如 parent.appendChild(fragment))。 | 插入时只插入片段的子节点,DocumentFragment 自身不保留 仅触发一次重排,显著优于逐个插入 适用于列表渲染、动态表单生成等批量场景 |
| 方法/属性 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| createDocumentFragment | const frag = document.createDocumentFragment(); | 创建文档片段实例。 | const frag = document.createDocumentFragment(); | 返回一个空的 DocumentFragment 对象 |
| appendChild (到片段) | frag.appendChild(node) | 向片段添加子节点。 | items.forEach(item => frag.appendChild(createItem(item))); | 节点被”转移”到片段中(若原在 DOM 中则被移除) |
| append (到片段,现代) | frag.append(...nodes) | 批量添加节点或文本到片段。 | frag.append(div1, textNode, 'static text'); | 支持字符串(自动转为 Text 节点) 更简洁,但 IE 不支持 |
六、事件处理基础
6.1 添加/移除事件监听(addEventListener / removeEventListener)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| addEventListener | target.addEventListener(type, listener, [options]) | 为元素注册一个事件监听器。 | button.addEventListener('click', handleClick); | type 是不带 “on” 前缀的事件名(如 'click')listener 必须是函数(不能是字符串) 支持多个监听器绑定同一事件 默认在冒泡阶段触发 |
| removeEventListener | target.removeEventListener(type, listener, [options]) | 移除已注册的事件监听器。 | button.removeEventListener('click', handleClick); | 必须传入完全相同的函数引用才能移除 匿名函数无法被移除 options 参数必须与 addEventListener 中一致(若使用) |
| options 参数(可选) | { capture: boolean, once: boolean, passive: boolean } | 控制监听器行为。 | el.addEventListener('scroll', handler, { passive: true }); | capture: true 表示在捕获阶段调用(默认 false)once: true 表示触发一次后自动移除passive: true 表示监听器不会调用 preventDefault()(提升滚动性能) |
6.2 常见 DOM 事件类型(click, input, load 等)
| 事件类型 | 触发时机 | 典型使用场景 | 注意事项 |
|---|---|---|---|
| click | 用户点击元素(按下并释放) | 按钮点击、菜单交互 | 在 touch 设备上可能有 ~300ms 延迟(可通过 CSS touch-action: manipulation 或库解决) |
| dblclick | 用户双击元素 | 打开/编辑项 | 不常用,需注意与 click 冲突(双击会触发两次 click) |
| input | <input>、<textarea>、<select> 的值发生变化时 | 实时搜索、表单验证 | 在用户输入过程中持续触发(比 change 更即时) 不冒泡(但实际浏览器中通常冒泡) |
| change | 表单控件失去焦点且值已改变 | 提交表单前校验 | 对 <select> 选择即触发;对文本输入需失焦才触发 |
| load | 资源(如 window、img、script)加载完成 | 图片懒加载完成、页面初始化 | window.onload 等待所有资源(包括图片)加载完毕<img>.onload 可用于检测图片是否加载成功 |
| DOMContentLoaded | HTML 文档完全加载和解析完成(不等待样式表、图片等) | 初始化 DOM 操作 | 比 window.onload 更早触发推荐在此事件中执行 JS 初始化逻辑 |
| keydown / keyup | 键盘按键按下 / 释放 | 快捷键、游戏控制 | keydown 可检测组合键(如 Ctrl+K)event.key 或 event.code 获取按键信息 |
| focus / blur | 元素获得 / 失去焦点 | 表单高亮、输入提示 | focus 不冒泡(但可用 focusin/focusout 替代)只有可聚焦元素(如 input、button)能触发 |
| submit | 表单提交时(通过按钮或回车) | 表单数据验证与提交 | 可在监听器中调用 event.preventDefault() 阻止默认提交绑定在 <form> 元素上 |
6.3 事件对象与冒泡/捕获机制简介
(1)事件对象(Event Object)常用属性与方法
| 属性/方法 | 说明 | 代码示例 | 注意事项 |
|---|---|---|---|
| event.type | 事件类型(如 'click') | console.log(e.type); | 字符串,只读 |
| event.target | 实际触发事件的元素(最底层元素) | if (e.target.matches('.btn')) { ... } | 与 this(当前监听器绑定的元素)可能不同(因事件冒泡) |
| event.currentTarget | 当前事件监听器所绑定的元素 | console.log(e.currentTarget === this); // true | 始终等于监听器中的 this |
| event.preventDefault() | 阻止事件的默认行为 | e.preventDefault(); // 阻止链接跳转 | 如阻止表单提交、链接跳转等 被标记为 passive: true 的监听器中调用无效 |
| event.stopPropagation() | 阻止事件继续冒泡(或捕获) | e.stopPropagation(); | 不影响同一元素上的其他监听器 无法阻止已触发的默认行为 |
| event.stopImmediatePropagation() | 阻止后续监听器执行 + 阻止冒泡 | e.stopImmediatePropagation(); | 同一事件类型在同一元素上的后续监听器不会执行 |
(2)事件流:捕获 vs 冒泡
| 阶段 | 顺序 | 说明 | 使用方式 |
|---|---|---|---|
| 捕获阶段(Capture) | 从 window → document → … → 目标父元素 | 事件向下传递到目标元素 | addEventListener(type, fn, true) 或 { capture: true } |
| 目标阶段(Target) | 到达目标元素 | 执行目标元素上的监听器 | 无论 capture 为何值都会执行 |
| 冒泡阶段(Bubble) | 从目标父元素 → … → document → window | 事件向上传播 | 默认行为(capture: false) |
| 注意事项 |
|---|
| 大多数事件支持冒泡(如 click、input),但有些不支持(如 focus、blur) |
| 推荐优先使用冒泡阶段,除非有特殊需求(如全局拦截) |
可通过 event.eventPhase 判断当前阶段(1=捕获,2=目标,3=冒泡) |