1. Selectors API(简要介绍)
Selectors API 是把 CSS 选择器直接用于 DOM 查找的统一接口,核心三个方法:
querySelector(selector)
返回匹配selector
的第一个元素(document 或任意 Element 上可用)。如果没有返回null
。适合查找单个元素或快速选择。querySelectorAll(selector)
返回所有匹配元素的 静态NodeList
(快照)。注意:返回的是静态节点集合,不会随着 DOM 修改自动更新。遍历或反复查询时注意性能。matches(selector)
检查当前元素是否匹配给定选择器,常用于事件委托中确认事件目标是否匹配。现代浏览器统一为matches
,历史上有前缀webkitMatchesSelector
/msMatchesSelector
等,可做兼容写法。
示例:
dart
const firstBtn = document.querySelector('.btn.primary');
const allItems = document.querySelectorAll('.list > li');
if (someElement.matches(':scope > .active')) { /* ... */ }
性能建议:对于简单单一查找(如按 id)优先 getElementById
;对大量选择或频繁操作,缓存查询结果或限制作用域(在容器 Element 上调用)可减少重绘/查询成本。
2. 元素遍历(通过这些 API 遍历树形)
核心属性/方法(只列元素节点相关,避免 text/comment):
element.children
--- 返回子元素集合(HTMLCollection
,是 live 的,DOM 变化会反映)。element.firstElementChild
/element.lastElementChild
--- 第一个/最后一个子元素(或null
)。element.nextElementSibling
/element.previousElementSibling
--- 同级的下/上一个元素节点(或null
)。
常见遍历模式(深度优先,元素节点):
scss
// 递归 DFS 遍历所有元素节点
function walk(el, cb) {
cb(el);
for (let child of el.children) walk(child, cb);
}
walk(document.body, el => console.log(el.tagName));
// 使用 nextElementSibling 遍历同级
let node = container.firstElementChild;
while (node) {
// 处理 node
node = node.nextElementSibling;
}
补充工具(更强):TreeWalker
/ NodeIterator
(用于复杂遍历过滤)------当你要在大量节点上做带过滤的遍历,TreeWalker 更高效并且语义更清晰。
注意 childNodes
与 children
的区别:childNodes
包含文本和注释节点;children
只包含元素节点(更常用)。
3. HTML5(与 DOM 扩展相关的要点)
HTML5 对 DOM 的扩展很多,这里重点和实用 API 相关的:
- 新语义元素(
<section>
,<article>
,<nav>
等)------对语义化、可访问性有利。 dataset
(data-*)与classList
、querySelector
全面支持,使 DOM 操作更灵活。contentEditable
、history
API(pushState/replaceState)及canvas
、audio
、video
等增强了页面交互能力。document.head
、document.body
等属性更规范、便于访问。
(HTML5 范围广,上面是与 DOM 直接相关的重要点。)
4. CSS 类扩展
4.1 getElementsByClassName
- 返回的是 live
HTMLCollection
(会随 DOM 变动自动更新)。 - 速度通常比
querySelectorAll
快(具体受实现影响),适合需要实时反映的场景,但在修改 DOM 的循环中请小心 live 集合带来的副作用(建议先做Array.from()
快照)。
4.2 classList
属性(DOMTokenList)
-
现代推荐操作类名的 API,语义清晰:
el.classList.add('a','b')
el.classList.remove('c')
el.classList.toggle('d')
或el.classList.toggle('e', condition)
el.classList.contains('f')
el.classList.replace('old','new')
-
classList
性能优于直接字符串拼接(避免错误空格、重复),并支持forEach
。
示例:
csharp
el.classList.add('is-visible');
if (!el.classList.contains('active')) el.classList.add('active');
el.classList.toggle('expanded', shouldExpand);
5. 焦点管理(focus)
关键点与最佳实践:
-
API:
element.focus([options])
、element.blur()
、document.activeElement
。element.focus({preventScroll: true})
(可选)可聚焦同时不触发滚动(现代浏览器支持)。
-
tabindex
:tabindex="-1"
:可以程序聚焦,但不可通过 Tab 导航。tabindex="0"
:参与顺序,位置由文档流决定(常用于自定义可聚焦组件)。- 正整数 tabindex(
>0
)会创建自定义顺序,不推荐使用(维护困难)。
-
可访问性(A11y):确保可视焦点样式(不要随意移除 outline),模态对话框需实现焦点陷阱(trap focus)和返回焦点。
-
焦点陷阱示例(简单):
ini
const focusable = modal.querySelectorAll('a, button, input, [tabindex]:not([tabindex="-1"])');
let first = focusable[0], last = focusable[focusable.length-1];
modal.addEventListener('keydown', e => {
if (e.key === 'Tab') {
if (e.shiftKey && document.activeElement === first) {
e.preventDefault(); last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault(); first.focus();
}
}
});
6. HTMLDocument 扩展
document.readyState
:常见值loading
/interactive
/complete
。在interactive
时 DOM 已可操作但外部资源(图片)可能未完全加载;DOMContentLoaded
触发时通常为interactive
。document.compatMode
:"CSS1Compat"
(标准模式)或"BackCompat"
(怪异模式)。用于判断文档是否以标准渲染。document.head
:直接访问<head>
节点的快捷属性(等价于document.getElementsByTagName('head')[0]
)。
实践:在绑定 DOMContentLoaded 时,可先检查 document.readyState !== 'loading'
以避免重复等待。
7. 字符集属性(characterSet
详解)
document.characterSet
(有时也见document.charset
)返回文档使用的字符编码字符串,例如"UTF-8"
。- 这是只读的,决定于 HTTP 头或文档内
<meta charset="...">
。解析阶段确定,运行时更改不会重新解析页面。 - 用途:调试/检测文本编码问题或服务器端生成页面时确认编码;一般无需频繁使用。
8. 自定义数据属性(data-*
)
- HTML:
<div data-user-id="123" data-x-y-z="value"></div>
- JS:通过
element.dataset
访问(DOMStringMap
),属性名由kebab-case
转为camelCase
:data-user-id
→dataset.userId
,data-x-y-z
→dataset.xYZ
(注意大小写转换)。 - 值为字符串;如果要存对象/数组,需序列化(
JSON.stringify
/JSON.parse
)。 - 注意:不要把敏感数据(令牌、密码)放到 DOM 可见属性上------它对前端所有脚本和恶意扩展可见。
示例:
ini
el.dataset.userId = '123';
console.log(el.dataset.xYz); // 读取 data-x-y-z
9. 插入标记(innerHTML / outerHTML / insertAdjacent* / 性能与安全)
9.1 innerHTML
- 读取返回元素内部的 HTML 字符串,赋值会 替换子节点 并重新解析 HTML。
- 优点:简单、直观。缺点:可能导致大量重排、事件监听丢失(因为旧节点被销毁)、XSS 风险。
- 脚本插入:通过
innerHTML
插入<script>
通常不会 直接执行(不同环境历史行为有差异),若需执行脚本应使用createElement('script')
并 append。
9.2 旧 IE 中的 innerHTML
- 旧 IE(历史)在对
table
、select
等元素的innerHTML
支持上有奇怪差异(例如自动插入tbody
),现代浏览器已较统一。对兼容性要求高的项目需注意旧浏览器行为。
9.3 outerHTML
- 代表元素自身及其内部的 HTML 字符串。赋值会替换当前元素(包括自身)。
9.4 insertAdjacentHTML
与 insertAdjacentText
-
element.insertAdjacentHTML(position, htmlString)
:在指定位置插入解析后的节点。position
为:'beforebegin'
,'afterbegin'
,'beforeend'
,'afterend'
-
insertAdjacentText
是在指定位置插入纯文本(不会解析为 HTML),更安全。 -
insertAdjacentHTML
相比innerHTML
的优点:不会替换父节点的全部内容,通常性能更好且保留现有节点和监听器(插入位置明确),常用作局部更新。
示例:
arduino
container.insertAdjacentHTML('beforeend', '<li class="item">新项</li>');
container.insertAdjacentText('beforeend', '纯文本,不会解析成 HTML');
9.5 内存与性能问题
-
每次修改 DOM 都可能触发重排(reflow)和重绘(repaint)。最佳实践:
- 批量 DOM 更新时先构建
DocumentFragment
或字符串一次性插入。 - 对频繁更新的 UI 使用虚拟 DOM、模板或最小化 DOM 变更。
- 使用
requestAnimationFrame
来调度视觉变化,避免在短时间内多次强制布局。 - 当需要创建大量节点时,
Range.createContextualFragment()
或document.createDocumentFragment()
比多次appendChild
更高效。
- 批量 DOM 更新时先构建
示例(DocumentFragment):
ini
const frag = document.createDocumentFragment();
for (let i=0;i<1000;i++) {
const li = document.createElement('li');
li.textContent = `项 ${i}`;
frag.appendChild(li);
}
ul.appendChild(frag); // 一次性插入,减少重排
9.6 跨站脚本(XSS)
- 直接将用户输入插入
innerHTML
/insertAdjacentHTML
风险极高。避免将未消毒的字符串作为 HTML 插入。 - 方案:对输入进行白名单过滤或在服务器端清洗;在前端使用文本节点(
textContent
/createTextNode
)插入纯文本;或使用成熟的库(如 DOMPurify)对 HTML 做消毒再插入。 - 不要用正则简单替换
<script>
等做 XSS 过滤------很容易绕过。
10. scrollIntoView
方法
- 简单调用:
el.scrollIntoView()
(旧签名)会滚动最近的可滚动祖先和视口,使元素可见。 - 现代签名提供选项对象:
el.scrollIntoView({ behavior: 'auto'|'smooth', block: 'start'|'center'|'end'|'nearest', inline: 'nearest'|'start'|'center'|'end' })
。 - 常见用法:
php
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
- 若你只想在元素不可见时滚动,可先检查是否可见(用
getBoundingClientRect()
或IntersectionObserver
),再调用scrollIntoView
。否则会造成不必要的跳转/滚动抖动。
11. 专有扩展(历史与兼容性)
-
历史上不同浏览器曾引入专有 API 或前缀方法,例如:
element.attachEvent
(旧 IE) vsaddEventListener
webkitMatchesSelector
/msMatchesSelector
→ 现在都可用matches
outerText
(IE)为非标准属性(一些浏览器实现)scrollIntoViewIfNeeded
(WebKit/Blink 的非标准扩展)
-
建议:尽量使用标准 API 并在必要时提供逐级兼容处理(feature-detect 或 polyfill)。
12. children
属性(补充说明)
(已在遍历部分提到)
要点回顾:
children
只返回元素节点(Element
),是HTMLCollection
(live)。- 如果要静态快照用于修改,请转换为数组
Array.from(el.children)
避免 live 集合带来的迭代问题。
13. contains
/ 常量(可能为你说的 "contants方法" 的两重含义)
Node.contains
parent.contains(node)
返回true
当且仅当node
是parent
的后代 或等于 parent 自身(注意:包含自身时也返回 true)。
javascript
console.log(document.body.contains(someNode));
console.log(elem.contains(elem)); // true
节点类型常量(Node.*)
Node.ELEMENT_NODE
(1)、Node.TEXT_NODE
(3)、Node.COMMENT_NODE
(8)等常量用于判断node.nodeType
。比硬编码数字更可读:
ini
if (node.nodeType === Node.ELEMENT_NODE) { /* 是元素 */ }
文档顺序比较
nodeA.compareDocumentPosition(nodeB)
返回位掩码,可以检测包含关系、前后位置等(高级用法)。常用于实现稳定排序或比较节点文档顺序。
14. 插入标记(innerText
/ outerText
)
-
textContent
vsinnerText
:textContent
:返回/设置元素及子孙的文本内容(不考虑 CSS),性能快,不触发布局计算。innerText
:返回"渲染"出来的文本,受 CSS(display:none
、::before
/::after
)影响;读取innerText
会触发重排以计算可见文本,较慢。innerText
源于旧 IE,现已被广泛实现。
-
outerText
:非标准属性(在某些浏览器可用)。赋值通常会替换元素自身为文本。
推荐:若只需要取/设置纯文本,用 textContent
;若需要按可视文本(考虑 CSS)用 innerText
并注意性能开销。
15. 滚动(scrollIntoViewIfNeeded
与更深的思考)
-
scrollIntoViewIfNeeded
:这是一个 非标准 的 API(曾在 WebKit/Blink 中存在),用于"仅当元素不可见时"滚动到视口中心或靠近边缘。不可依赖于所有平台/浏览器。 -
建议替代方案:
- 使用
IntersectionObserver
检测元素是否可见;在不可见时调用scrollIntoView({ behavior:'smooth', block:'center' })
。IntersectionObserver
更高效、不会强制布局。 - 直接用
getBoundingClientRect()
与视口尺寸做检测(简单但要谨慎,可能触发布局读取)。 - 对于复杂场景(内嵌滚动容器),计算容器的滚动位置并设置
scrollTop
/scrollLeft
,或使用Element.scrollTo({ top, behavior })
。
- 使用
示例(IntersectionObserver + 自动滚动):
ini
const obs = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (!e.isIntersecting) {
e.target.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
});
}, { threshold: 0.9 }); // 0.9 表示至少 90% 可见
obs.observe(targetElement);
深层思考要点:
-
自动滚动行为会影响用户体验(打断上下文、改变阅读位置),因此:
- 优先使用"只在必要时滚动"的策略(检测可见性)。
- 在可访问性场景(焦点移动)保证视觉跟随焦点,但考虑
preventScroll
选项或平滑滚动以降低突兀感。
-
对于虚拟列表/长列表(如 infinite scroll),应结合 "可见性检测 + 按需渲染 + 回收节点" 的策略,而非简单地滚动到某处插入大量 DOM。
额外实战建议(速览)
- 避免频繁
innerHTML
造成的重排与事件丢失;使用DocumentFragment
、insertAdjacentHTML
、或构造元素然后一次性 append。 - 为大量动态元素使用事件委托(在容器上绑定一个 listener),减少内存和绑定开销。
- 操作 NodeList / HTMLCollection 时注意 live vs static:若中间修改 DOM,先 snapshot
Array.from(...)
。 - XSS:绝不要把用户输入直接通过
innerHTML
插入。使用textContent
或可信的 sanitizer。 - 测试各种浏览器的边界行为(尤其在面对旧浏览器或嵌入式浏览器时)。