前端进阶必看:你真的懂 DOM 吗?(超全总结)

1. 节点层级(Node tree)

  • DOM 是一棵树:Document (根)→ Element (元素)→ Text/Comment 等。

  • 常见 nodeType

    • 1:Element
    • 3:Text
    • 8:Comment
    • 9:Document
    • 10:DocumentType(<!DOCTYPE html>
    • 11:DocumentFragment
    • 2:Attr(属性节点,遗留

2. 节点类型与 Node 常用属性/关系

2.1 nodeNamenodeValue

  • nodeName:标签名或节点名(元素为大写标签名如 "DIV";Text 为 #text;Comment 为 #comment)。

  • nodeValue

    • Element:null
    • Text:文本内容
    • Comment:注释文本
ini 复制代码
const div = document.createElement('div');
console.log(div.nodeName, div.nodeValue); // "DIV", null

const t = document.createTextNode('hello');
console.log(t.nodeName, t.nodeValue); // "#text", "hello"

2.2 节点关系

  • 父子:parentNodefirstChildlastChildchildNodes(NodeList,包含文本/注释)
  • 同级:previousSiblingnextSibling
  • 仅元素的集合:childrenfirstElementChildlastElementChildpreviousElementSiblingnextElementSibling
ini 复制代码
const ul = document.querySelector('ul');
ul.firstElementChild; // 第一个 <li>
ul.childNodes;        // 包含换行产生的 Text 节点

2.3 操作节点(插入/删除/替换/移动)

  • 经典 API:appendChildinsertBefore(newNode, refNode)replaceChild(newNode, oldNode)removeChild(node)
  • 现代便捷:append(...)prepend(...)before(...)after(...)replaceWith(...)remove()
ini 复制代码
const li = document.createElement('li');
li.textContent = 'Item';
ul.appendChild(li);

const first = ul.firstElementChild;
first.before('标题:');          // 插到元素前
first.replaceWith(li);          // 替换
li.remove();                    // 删除

2.4 其它方法(克隆等)

  • cloneNode(deep):浅/深拷贝节点(deep=true 拷贝子树)。
  • contains(node):是否为后代
  • compareDocumentPosition(node):文档位置关系(位掩码)
  • isEqualNode(node)/isSameNode(node)(后者≈===
go 复制代码
const copy = ul.cloneNode(true);  // 深拷贝整个列表
document.body.append(copy);

3. Document 类型

3.1 典型属性

  • nodeType=9nodeName="#document"
  • documentElement(根 <html>)、headbodydoctype
  • URLdomaintitlereferrerlastModifiedcookiecompatModeCSS1Compat / BackCompat
ini 复制代码
document.title = '新标题';
console.log(document.documentElement === document.childNodes[1]); // 通常 true(childNodes[0] 可能是 doctype)

3.1′ 文档子节点

  • document.childNodes[0] 常为 DocumentType<!DOCTYPE html>
  • document.childNodes[1] 通常为 <html>documentElement

3.2 文档信息

javascript 复制代码
console.log(document.URL, document.domain, document.referrer, document.lastModified);

3.3 定位元素(基于名字/ID的集合)

  • 某些集合 支持按 nameid 访问:如 document.images['nameTest']
  • 现代更推荐:querySelector / getElementById

3.4 特殊集合(HTMLCollection,部分是动态/live)

  • document.anchors<a name="...">遗留 ;现代多用 id
  • document.applets废弃
  • document.forms:所有 <form>
  • document.images:所有 <img>
  • document.links:所有带 href<a>

⚠️ 这些多为 动态 集合(DOM 变化会实时反映),但 querySelectorAll 返回 静态 NodeList。

3.5 DOM 兼容性检测(特性检测)

  • 推荐:判断 API 是否存在
javascript 复制代码
if ('querySelector' in document && 'classList' in Element.prototype) {
  // 安全使用
}
  • CSS.supports('display', 'grid') 检测 CSS 支持
  • ⚠️ document.implementation.hasFeature() 已不可靠

3.6 文档写入:write / writeln(了解即可)

  • 仅在解析阶段 使用;在加载后调用可能清空文档并重写
xml 复制代码
<script>
  document.write('<p>构建时写入</p>');
  document.writeln('<p>带换行</p>');
</script>

⚠️ 现代开发中应避免,改用 DOM API 或模板渲染。


4. Element 类型

4.1 常用属性

  • 结构:tagNameidclassNameclassListdatasetattributes(NamedNodeMap,遗留 )、innerHTMLouterHTMLtextContent
  • 尺寸/位置(只读/计算):clientWidth/HeightoffsetTop/LeftgetBoundingClientRect()
ini 复制代码
const div = document.createElement('div');
div.id = 'box';
div.classList.add('card', 'primary');
div.dataset.role = 'panel';
div.innerHTML = '<strong>Hi</strong>';
document.body.append(div);

4.2 获取属性

less 复制代码
div.getAttribute('data-role'); // "panel"
div.hasAttribute('hidden');    // boolean

4.3 设置/移除属性

less 复制代码
div.setAttribute('aria-live', 'polite');
div.removeAttribute('aria-live');

4.4 attributes(NamedNodeMap,不推荐新代码依赖

  • 方法:getNamedItem(name)setNamedItem(attr)removeNamedItem(name)item(index)
ini 复制代码
const attr = document.createAttribute('data-x');
attr.value = '1';
div.attributes.setNamedItem(attr);

现代用 getAttribute/setAttribute/hasAttribute/removeAttribute 更简单。

4.5 创建元素与插入

ini 复制代码
const li = document.createElement('li');
li.textContent = 'A';
document.querySelector('ul').appendChild(li);

4.6 元素的后代

  • childNodes(含文本/注释)
  • children(仅元素)
  • 遍历推荐:for (const el of element.children) {}element.querySelectorAll(':scope > *')

5. Text 类型

  • 创建:document.createTextNode('text')

  • 规范化:parent.normalize() 合并相邻 Text 节点

  • 拆分:text.splitText(offset)

  • 字符数据方法(Text 继承自 CharacterData):

    • appendData(s)deleteData(offset,count)insertData(offset,s)
    • replaceData(offset,count,s)substringData(offset,count)

小型富文本示例(基于选区拆分/替换)

ini 复制代码
<div id="editable" contenteditable="true">前缀<strong>粗体</strong>后缀</div>
<script>
const el = document.getElementById('editable');

// 将选区用 <mark> 包裹(简化演示,未处理跨节点复杂情况)
function highlightSelection() {
  const sel = window.getSelection();
  if (!sel.rangeCount) return;
  const range = sel.getRangeAt(0);

  const frag = range.extractContents();           // 取出选中内容(可能含多个节点)
  const mark = document.createElement('mark');
  mark.append(frag);
  range.insertNode(mark);
  sel.removeAllRanges();
}

document.addEventListener('keydown', e => {
  if (e.key.toLowerCase() === 'h' && (e.ctrlKey || e.metaKey)) {
    e.preventDefault();
    highlightSelection();
  }
});
</script>

6. Comment 类型

  • 创建与使用:
ini 复制代码
const c = document.createComment('TODO: remove in prod');
document.body.append(c);
console.log(c.nodeType, c.nodeValue); // 8, "TODO: ..."

7. CDATASection 类型

  • 仅适用于 XML/XHTML 文档。在 HTML 文档中 createCDATASection 会抛出 NotSupportedError
ini 复制代码
// 在 XML DOM 中:
const xmlDoc = document.implementation.createDocument(null, 'root');
const cdata = xmlDoc.createCDATASection('<raw>&chars</raw>');
xmlDoc.documentElement.append(cdata);

8. DocumentType 类型(DOCTYPE)

javascript 复制代码
console.log(document.doctype.name); // "html"
  • 创建(多用于生成 XML/离线文档):
ini 复制代码
const dt = document.implementation.createDocumentType('html', '', '');
document.insertBefore(dt, document.documentElement);

9. DocumentFragment

  • 轻量级容器,不会产生额外父节点,插入时一次性移动其子节点(减少重排/重绘)。
ini 复制代码
const frag = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  const li = document.createElement('li');
  li.textContent = `Item ${i}`;
  frag.append(li);
}
document.querySelector('ul').append(frag);
  • 结合 <template>
xml 复制代码
<template id="tpl"><li class="row"><span></span></li></template>
<script>
const { content } = document.getElementById('tpl');
const ul = document.querySelector('ul');
const frag = document.createDocumentFragment();

for (const v of ['A','B','C']) {
  const node = content.cloneNode(true);
  node.querySelector('span').textContent = v;
  frag.append(node);
}
ul.append(frag);
</script>

10. Attr 类型(属性节点,遗留)

ini 复制代码
const attr = document.createAttribute('data-k');
attr.value = 'v';
const old = document.getElementById('box').setAttributeNode(attr);

新代码更推荐 setAttribute('data-k','v')


11. DOM 编程实战

11.1 动态脚本

ini 复制代码
function loadScript(src, { async=true, defer=false, module=false } = {}) {
  const s = document.createElement('script');
  if (module) s.type = 'module';
  s.src = src;
  s.async = async;
  s.defer = defer;
  return new Promise((res, rej) => {
    s.onload = () => res(s);
    s.onerror = rej;
    document.head.append(s);
  });
}
// 使用:await loadScript('/assets/app.js', { module: true });

11.2 动态样式

ini 复制代码
// 内联样式表
const style = document.createElement('style');
style.textContent = `
  .badge { padding: 4px 8px; border-radius: 8px; }
`;
document.head.append(style);

// 外链样式表
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = '/styles/app.css';
document.head.append(link);

11.3 操作表格(详细示例)

ini 复制代码
// 创建表格:thead / tbody / tfoot 与行列 API
const table = document.createElement('table');
table.createTHead();
const tbody = table.createTBody();
table.createTFoot();

// 头部
const hrow = table.tHead.insertRow();
['#','Name','Score'].forEach(t => hrow.insertCell().textContent = t);

// 数据
[
  { id:1, name:'Alice', score: 95 },
  { id:2, name:'Bob',   score: 87 },
].forEach(r => {
  const row = tbody.insertRow();
  row.insertCell().textContent = r.id;
  row.insertCell().textContent = r.name;
  row.insertCell().textContent = r.score;
});

// 脚注
const frow = table.tFoot.insertRow();
const cell = frow.insertCell();
cell.colSpan = 3;
cell.textContent = 'Total rows: ' + tbody.rows.length;

document.body.append(table);

// 更新:删除第二行
// tbody.deleteRow(1);

11.4 使用 NodeList

  • 来源:querySelectorAll静态 )、childNodes(可能 动态
  • 迭代:for...ofNodeList.prototype.forEach
  • 转数组与高阶方法:
ini 复制代码
const list = document.querySelectorAll('li'); // 静态
list.forEach(li => li.classList.add('x'));

const arr = Array.from(list).map(li => li.textContent);

12. MutationObserver 接口

用于异步观察 DOM 变化(微任务队列中回调)。

12.1 基本用法

  1. observe()
javascript 复制代码
const target = document.getElementById('root');
const obs = new MutationObserver((mutations, observer) => {
  for (const m of mutations) {
    if (m.type === 'childList') {
      console.log('childList:', m.addedNodes, m.removedNodes);
    } else if (m.type === 'attributes') {
      console.log('attr:', m.attributeName, 'old=', m.oldValue, 'new=', target.getAttribute(m.attributeName));
    } else if (m.type === 'characterData') {
      console.log('text old=', m.oldValue, 'new=', m.target.data);
    }
  }
});

obs.observe(target, {
  childList: true,
  attributes: true,
  characterData: true,
  subtree: true,
  attributeOldValue: true,
  characterDataOldValue: true,
  attributeFilter: ['class','data-state'] // 可选
});
  1. 回调与 MutationRecord
  • type'attributes'|'characterData'|'childList'
  • target:发生变化的节点
  • addedNodes/removedNodeschildList
  • attributeNameoldValue(需启用对应 OldValue
  • previousSibling/nextSibling(在子节点变化时有值)
  1. disconnect() 停止观察
ini 复制代码
obs.disconnect();
  1. 复用/重用同一观察者
php 复制代码
// 监听多个不同节点或范围
const nodes = document.querySelectorAll('.watch');
for (const n of nodes) obs.observe(n, { attributes:true, childList:true });

12.2 MutationObserverInit 与观察范围

  • attributes:属性变化
  • characterData:文本节点数据变化
  • childList:子节点增删
  • subtree:是否包含整个子树
  • attributeOldValue:记录属性旧值(需 attributes: true
  • characterDataOldValue:记录文本旧值(需 characterData: true
  • attributeFilter:仅关注特定属性数组

12.3 异步回调与记录队列

  • 变化会进入记录队列 ,在当前任务结束后以微任务回调一并派发。
  • takeRecords():立即取出队列中未派发的记录(不触发回调)。
ini 复制代码
const pending = obs.takeRecords();
pending.forEach(r => console.log('手动取:', r));

12.4 性能、内存与垃圾回收

  • 避免对 document 根节点全量 subtree:true, childList:true 的长期观察;改为缩小范围或精确 attributeFilter
  • 回调中避免频繁 DOM 写操作(可批处理requestAnimationFrame 合并)。
  • 及时 disconnect(),避免泄漏;不要在回调里递归触发大量新变更(可能引发振荡)。
  • 将观察者实例存在需要的作用域;不再使用时移除引用,让 GC 回收。

附:常用对照与最佳实践

  • 取元素getElementById(快)/ querySelector(灵活)
  • 遍历孩子 :结构化用 children,包含文本则用 childNodes
  • 创建+插入 :批量用 DocumentFragment;单个用 append/prepend/before/after
  • 属性 :首选 getAttribute/setAttribute/hasAttribute/removeAttribute 或直接属性(如 el.idel.className),少用 attributes
  • 文本 :首选 textContent(安全、不会解析 HTML);需要 HTML 时用 innerHTML(注意 XSS)
  • 旧特性document.writeanchorsappletsNamedNodeMapAttr 节点 ------ 了解即可,不建议新项目依赖
相关推荐
wow_DG1 分钟前
【Vue2 ✨】Vue2 入门之旅 · 进阶篇(八):Vuex 内部机制
前端·javascript·vue.js
若年封尘2 分钟前
吃透 Vue 样式穿透:从 scoped 原理到组件库样式修改实战
前端·javascript·vue.js·样式穿透·scoped
掘金安东尼12 分钟前
CSS 颜色混乱实验
前端·javascript·github
Zhen (Evan) Wang14 分钟前
.NET 6 文件下载
java·前端·.net
前端码农.16 分钟前
Element Plus 数字输入框箭头隐藏方案
前端·vue.js
李游Leo23 分钟前
npm / yarn / pnpm 包管理器对比与最佳实践(含国内镜像源配置与缓存优化)
前端·缓存·npm
Mintopia33 分钟前
轻量化AIGC模型在移动端Web应用的适配技术
前端·javascript·aigc
Mintopia33 分钟前
Next.js CI/CD 基础(GitHub Actions)
前端·javascript·next.js
Wiktok2 小时前
pureadmin的动态路由和静态路由
前端·vue3·pureadmin
devii662 小时前
html.
前端