编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • C语言入门精通

  • Cpp入门到精通

  • Java入门精通

  • Go入门到精通

  • JavaScript入门

    • 基础入门

      • README
      • 入门介绍
      • 数据类型
      • 运算符
      • 函数
      • 面向对象
      • 标准库
      • 异步操作
      • 事件设计
      • 错误机制
      • 模块开发
      • 字符串处理
      • 迭代器与生成器
      • Symbol
      • DOM操作
        • 14.1 DOM 树结构
          • 14.1.1 节点类型
          • 14.1.2 节点关系
          • 14.1.3 Document 对象
        • 14.2 元素查找
          • 14.2.1 基础查找方法
          • 14.2.2 CSS 选择器查找
          • 14.2.3 closest 和 matches
        • 14.3 元素创建与操作
          • 14.3.1 创建节点
          • 14.3.2 插入节点
          • 14.3.3 删除和替换
          • 14.3.4 DocumentFragment 批量操作
        • 14.4 属性操作
          • 14.4.1 标准属性
          • 14.4.2 classList API
          • 14.4.3 dataset 自定义数据属性
          • 14.4.4 通用属性方法
        • 14.5 样式操作
          • 14.5.1 内联样式
          • 14.5.2 计算样式
          • 14.5.3 CSS 变量操作
          • 14.5.4 操作样式表
        • 14.6 元素尺寸与位置
          • 14.6.1 尺寸属性
          • 14.6.2 位置属性
          • 14.6.3 滚动操作
          • 14.6.4 判断元素可见
        • 14.7 MutationObserver
          • 14.7.1 基本用法
          • 14.7.2 实际应用
        • 14.8 ResizeObserver
        • 14.9 性能优化
          • 14.9.1 减少重排(Reflow)
          • 14.9.2 requestAnimationFrame
          • 14.9.3 虚拟 DOM 的核心思想
          • 14.9.4 DOM 操作性能建议
      • 网络请求
    • 综合案例

    • 专栏博客

  • CodeX
  • JavaScript入门
  • 基础入门
杨充
2026-04-13
目录

DOM操作

# 14.DOM操作

DOM(Document Object Model)是 JavaScript 操作网页的核心 API。浏览器将 HTML 解析为树形结构的节点对象,JavaScript 通过 DOM API 动态修改页面内容、结构和样式。掌握 DOM 操作是前端开发的基本功。

# 14.1 DOM 树结构

# 14.1.1 节点类型

DOM 中一切皆节点(Node),常见的节点类型:

节点类型 nodeType nodeName 示例 说明
Element 1 'DIV' HTML 元素
Text 3 '#text' 文本内容
Comment 8 '#comment' 注释
Document 9 '#document' 文档根节点
DocumentFragment 11 '#document-fragment' 文档片段
const div = document.createElement('div');
console.log(div.nodeType);  // 1
console.log(div.nodeName);  // 'DIV'

const text = document.createTextNode('hello');
console.log(text.nodeType); // 3

// 检查节点类型
if (node.nodeType === Node.ELEMENT_NODE) {
    // 是元素节点
}
1
2
3
4
5
6
7
8
9
10
11

# 14.1.2 节点关系

// 假设 HTML:<ul id="list"><li>A</li><li>B</li><li>C</li></ul>
const list = document.getElementById('list');

// 父节点
list.parentNode;       // 父节点(可能是任何类型)
list.parentElement;    // 父元素节点(跳过非元素节点)

// 子节点
list.childNodes;       // NodeList(包含文本节点、注释等)
list.children;         // HTMLCollection(仅元素节点)
list.firstChild;       // 第一个子节点(可能是文本)
list.firstElementChild; // 第一个子元素
list.lastChild;
list.lastElementChild;

// 兄弟节点
const li = list.children[1]; // 第二个 <li>
li.previousSibling;          // 前一个节点(可能是文本)
li.previousElementSibling;   // 前一个元素
li.nextSibling;
li.nextElementSibling;

// 子元素数量
list.childElementCount; // 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

注意:HTML 中的空白和换行会生成文本节点,使用 children、firstElementChild 等属性可以跳过这些文本节点。

# 14.1.3 Document 对象

// 文档信息
document.title;              // 页面标题
document.URL;                // 当前 URL
document.domain;             // 域名
document.referrer;           // 来源页面
document.readyState;         // 'loading' | 'interactive' | 'complete'
document.characterSet;       // 'UTF-8'
document.contentType;        // 'text/html'

// 特殊元素引用
document.documentElement;    // <html>
document.head;               // <head>
document.body;               // <body>
document.forms;              // 所有 <form>
document.images;             // 所有 <img>
document.links;              // 所有 <a href>
document.scripts;            // 所有 <script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 14.2 元素查找

# 14.2.1 基础查找方法

// 通过 ID(返回单个元素)
const el = document.getElementById('myId');

// 通过类名(返回 HTMLCollection,实时更新)
const items = document.getElementsByClassName('item');

// 通过标签名(返回 HTMLCollection)
const divs = document.getElementsByTagName('div');

// 通过 name 属性
const inputs = document.getElementsByName('email');
1
2
3
4
5
6
7
8
9
10
11

# 14.2.2 CSS 选择器查找

现代开发推荐使用 querySelector 系列:

// 返回第一个匹配元素
document.querySelector('#myId');
document.querySelector('.item');
document.querySelector('div.container > p:first-child');
document.querySelector('[data-id="123"]');

// 返回所有匹配元素(静态 NodeList)
document.querySelectorAll('.item');
document.querySelectorAll('input[type="text"]');

// 在特定元素内查找
const container = document.querySelector('.container');
container.querySelector('.child');
container.querySelectorAll('li');
1
2
3
4
5
6
7
8
9
10
11
12
13
14

HTMLCollection vs NodeList:

特性 HTMLCollection NodeList
返回方法 getElementsBy* querySelectorAll
实时性 实时更新 静态快照
包含类型 仅元素 所有节点
forEach 不支持 支持
索引访问 [i] 或 item(i) [i] 或 item(i)
// HTMLCollection 是实时的
const items = document.getElementsByClassName('item');
console.log(items.length); // 3

document.querySelector('.item').remove();
console.log(items.length); // 2(自动更新!)

// NodeList(querySelectorAll)是静态的
const nodes = document.querySelectorAll('.item');
// 即使 DOM 变化,nodes 不变
1
2
3
4
5
6
7
8
9
10

# 14.2.3 closest 和 matches

// matches - 检查元素是否匹配选择器
const el = document.querySelector('li');
el.matches('.active');     // true/false
el.matches('ul > li');     // true/false

// closest - 向上查找最近的匹配祖先(含自身)
el.closest('.container');  // 最近的 .container 祖先
el.closest('ul');          // 最近的 <ul> 祖先
el.closest('li');          // 自身(如果匹配)

// 实际应用:事件委托中定位目标元素
document.addEventListener('click', (e) => {
    const button = e.target.closest('button[data-action]');
    if (button) {
        const action = button.dataset.action;
        handleAction(action);
    }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 14.3 元素创建与操作

# 14.3.1 创建节点

// 创建元素
const div = document.createElement('div');
div.className = 'container';
div.id = 'main';

// 创建文本
const text = document.createTextNode('Hello World');

// 创建文档片段(用于批量操作)
const fragment = document.createDocumentFragment();

// 克隆节点
const clone = div.cloneNode(false);  // 浅克隆(不含子节点)
const deep = div.cloneNode(true);    // 深克隆(含子节点)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 14.3.2 插入节点

const parent = document.querySelector('.container');
const newEl = document.createElement('div');

// 末尾追加
parent.appendChild(newEl);

// 在参考节点前插入
const ref = parent.children[1];
parent.insertBefore(newEl, ref);

// 现代 API(更灵活)
parent.append(newEl, 'text', anotherEl);     // 末尾追加(支持多个/文本)
parent.prepend(newEl);                        // 开头插入
ref.before(newEl);                            // 在 ref 前面
ref.after(newEl);                             // 在 ref 后面

// insertAdjacentHTML - 解析 HTML 字符串并插入
parent.insertAdjacentHTML('beforebegin', '<p>Before</p>'); // 元素前面
parent.insertAdjacentHTML('afterbegin', '<p>First</p>');   // 元素内部开头
parent.insertAdjacentHTML('beforeend', '<p>Last</p>');     // 元素内部末尾
parent.insertAdjacentHTML('afterend', '<p>After</p>');     // 元素后面

// insertAdjacentElement / insertAdjacentText
parent.insertAdjacentElement('beforeend', newEl);
parent.insertAdjacentText('beforeend', 'some text');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 14.3.3 删除和替换

// 删除
const el = document.querySelector('.to-remove');
el.remove();                    // 现代方式
el.parentNode.removeChild(el);  // 传统方式

// 替换
const oldEl = document.querySelector('.old');
const newEl = document.createElement('div');
oldEl.replaceWith(newEl);                   // 现代方式
oldEl.parentNode.replaceChild(newEl, oldEl); // 传统方式

// 清空所有子节点
parent.innerHTML = '';       // 简单但有安全隐患
parent.textContent = '';     // 更安全
parent.replaceChildren();   // 最现代的方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 14.3.4 DocumentFragment 批量操作

使用 DocumentFragment 避免多次重排(reflow):

// 不推荐:每次 appendChild 都触发重排
const list = document.querySelector('#list');
for (let i = 0; i < 1000; i++) {
    const li = document.createElement('li');
    li.textContent = `Item ${i}`;
    list.appendChild(li); // 触发 1000 次重排
}

// 推荐:DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
    const li = document.createElement('li');
    li.textContent = `Item ${i}`;
    fragment.appendChild(li);
}
list.appendChild(fragment); // 只触发 1 次重排

// 或使用 replaceChildren
const items = Array.from({ length: 1000 }, (_, i) => {
    const li = document.createElement('li');
    li.textContent = `Item ${i}`;
    return li;
});
list.replaceChildren(...items);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 14.4 属性操作

# 14.4.1 标准属性

HTML 标准属性直接映射为 DOM 属性:

const input = document.querySelector('input');

// 读写属性
input.id = 'username';
input.type = 'text';
input.value = 'hello';
input.disabled = true;
input.checked = false;

// class 属性比较特殊
input.className = 'form-input active';

// href 返回完整 URL
const a = document.querySelector('a');
a.href;                    // 'https://example.com/page'(完整URL)
a.getAttribute('href');    // '/page'(原始值)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 14.4.2 classList API

const el = document.querySelector('.box');

el.classList.add('active', 'visible');
el.classList.remove('hidden');
el.classList.toggle('active');          // 切换
el.classList.toggle('active', true);    // 强制添加
el.classList.toggle('active', false);   // 强制移除
el.classList.contains('active');        // 检查
el.classList.replace('old', 'new');     // 替换

// 遍历
for (const cls of el.classList) {
    console.log(cls);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 14.4.3 dataset 自定义数据属性

// HTML: <div data-user-id="123" data-role="admin">
const el = document.querySelector('div');

// 读取(自动转为 camelCase)
el.dataset.userId;  // '123'
el.dataset.role;    // 'admin'

// 设置
el.dataset.newAttr = 'value';
// → <div data-new-attr="value">

// 删除
delete el.dataset.role;

// 遍历
for (const [key, value] of Object.entries(el.dataset)) {
    console.log(key, value);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 14.4.4 通用属性方法

// getAttribute / setAttribute - 操作 HTML 属性(字符串)
el.getAttribute('data-id');
el.setAttribute('aria-label', 'Close');
el.removeAttribute('hidden');
el.hasAttribute('disabled');

// 获取所有属性
for (const attr of el.attributes) {
    console.log(attr.name, attr.value);
}
1
2
3
4
5
6
7
8
9
10

# 14.5 样式操作

# 14.5.1 内联样式

const el = document.querySelector('.box');

// style 属性(只能读写内联样式)
el.style.color = 'red';
el.style.backgroundColor = '#fff';    // camelCase
el.style.fontSize = '16px';
el.style.cssText = 'color: red; font-size: 16px;'; // 批量设置

// 移除内联样式
el.style.color = '';
el.style.removeProperty('background-color'); // kebab-case

// 读取内联样式
el.style.color;  // 'red'(仅内联样式)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 14.5.2 计算样式

getComputedStyle 获取元素最终应用的样式(包含继承和 CSS 规则):

const el = document.querySelector('.box');
const computed = getComputedStyle(el);

computed.color;           // 'rgb(255, 0, 0)'
computed.fontSize;        // '16px'
computed.display;         // 'block'
computed.getPropertyValue('font-size'); // '16px'

// 获取伪元素样式
const pseudoStyle = getComputedStyle(el, '::before');
pseudoStyle.content;      // '"text"'
1
2
3
4
5
6
7
8
9
10
11

# 14.5.3 CSS 变量操作

// 设置 CSS 变量
document.documentElement.style.setProperty('--primary-color', '#007bff');
document.documentElement.style.setProperty('--font-size', '16px');

// 读取 CSS 变量
const root = getComputedStyle(document.documentElement);
root.getPropertyValue('--primary-color'); // '#007bff'

// 在元素级别覆盖
el.style.setProperty('--primary-color', '#ff0000');
1
2
3
4
5
6
7
8
9
10

# 14.5.4 操作样式表

// 访问样式表
document.styleSheets;                 // StyleSheetList
const sheet = document.styleSheets[0];
sheet.cssRules;                       // CSSRuleList

// 动态添加规则
sheet.insertRule('.new-class { color: red; }', sheet.cssRules.length);
sheet.deleteRule(0);

// 创建新样式表
const style = document.createElement('style');
style.textContent = `
    .dynamic { color: blue; }
`;
document.head.appendChild(style);

// 使用 CSSStyleSheet 构造器(现代方式)
const newSheet = new CSSStyleSheet();
newSheet.replaceSync('.modern { color: green; }');
document.adoptedStyleSheets = [...document.adoptedStyleSheets, newSheet];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 14.6 元素尺寸与位置

# 14.6.1 尺寸属性

const el = document.querySelector('.box');

// clientWidth/Height - 内容 + padding(不含 border 和 scrollbar)
el.clientWidth;
el.clientHeight;

// offsetWidth/Height - 内容 + padding + border + scrollbar
el.offsetWidth;
el.offsetHeight;

// scrollWidth/Height - 内容完整尺寸(包括溢出部分)
el.scrollWidth;
el.scrollHeight;
1
2
3
4
5
6
7
8
9
10
11
12
13

盒模型尺寸关系:

┌───────────────────── offsetWidth ─────────────────────┐
│ border                                                 │
│   ┌─────────────── clientWidth ──────────────┐         │
│   │ padding                                  │scrollbar│
│   │   ┌──────── content width ────────┐      │         │
│   │   │                               │      │         │
│   │   └───────────────────────────────┘      │         │
│   └──────────────────────────────────────────┘         │
└────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9

# 14.6.2 位置属性

// offsetTop/Left - 相对于 offsetParent 的偏移
el.offsetTop;
el.offsetLeft;
el.offsetParent; // 最近的定位祖先

// scrollTop/Left - 滚动偏移(可读写)
el.scrollTop;
el.scrollLeft;
el.scrollTop = 0; // 滚动到顶部

// getBoundingClientRect - 相对于视口的精确位置
const rect = el.getBoundingClientRect();
rect.top;     // 元素顶部到视口顶部的距离
rect.bottom;  // 元素底部到视口顶部的距离
rect.left;    // 元素左侧到视口左侧的距离
rect.right;   // 元素右侧到视口左侧的距离
rect.width;   // 元素宽度(= right - left)
rect.height;  // 元素高度(= bottom - top)
rect.x;       // 同 left
rect.y;       // 同 top
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 14.6.3 滚动操作

// 滚动到指定位置
window.scrollTo({ top: 0, behavior: 'smooth' });
el.scrollTo({ top: 100, left: 0, behavior: 'smooth' });

// 滚动指定距离
window.scrollBy({ top: 100, behavior: 'smooth' });

// 滚动元素到可见区域
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
// block: 'start' | 'center' | 'end' | 'nearest'
// inline: 'start' | 'center' | 'end' | 'nearest'

// 视口尺寸
window.innerWidth;   // 视口宽度(含滚动条)
window.innerHeight;  // 视口高度
document.documentElement.clientWidth;  // 视口宽度(不含滚动条)
document.documentElement.clientHeight;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 14.6.4 判断元素可见

// 方法1:getBoundingClientRect
function isInViewport(el) {
    const rect = el.getBoundingClientRect();
    return (
        rect.top < window.innerHeight &&
        rect.bottom > 0 &&
        rect.left < window.innerWidth &&
        rect.right > 0
    );
}

// 方法2:IntersectionObserver(推荐)
const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            console.log('元素可见:', entry.target);
            console.log('可见比例:', entry.intersectionRatio);
        }
    });
}, {
    root: null,         // 视口
    rootMargin: '0px',  // 提前触发的边距
    threshold: [0, 0.5, 1] // 触发回调的可见比例
});

observer.observe(el);
observer.unobserve(el);
observer.disconnect();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 14.7 MutationObserver

MutationObserver 监听 DOM 变化,替代已废弃的 Mutation Events:

# 14.7.1 基本用法

const observer = new MutationObserver((mutations) => {
    for (const mutation of mutations) {
        switch (mutation.type) {
            case 'childList':
                console.log('子节点变化');
                console.log('添加:', mutation.addedNodes);
                console.log('删除:', mutation.removedNodes);
                break;
            case 'attributes':
                console.log(`属性 ${mutation.attributeName} 变化`);
                console.log('旧值:', mutation.oldValue);
                break;
            case 'characterData':
                console.log('文本内容变化');
                console.log('旧值:', mutation.oldValue);
                break;
        }
    }
});

// 配置观察选项
observer.observe(document.body, {
    childList: true,       // 监听子节点增删
    attributes: true,      // 监听属性变化
    characterData: true,   // 监听文本内容变化
    subtree: true,         // 监听后代节点
    attributeOldValue: true,    // 记录属性旧值
    characterDataOldValue: true, // 记录文本旧值
    attributeFilter: ['class', 'style'], // 只监听指定属性
});

// 停止观察
observer.disconnect();

// 获取已排队但未处理的变化
const pending = observer.takeRecords();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 14.7.2 实际应用

监听 DOM 变化实现自动绑定:

// 自动为新增的懒加载图片添加 IntersectionObserver
const imgObserver = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            const img = entry.target;
            img.src = img.dataset.src;
            imgObserver.unobserve(img);
        }
    });
});

const domObserver = new MutationObserver((mutations) => {
    for (const mutation of mutations) {
        for (const node of mutation.addedNodes) {
            if (node.nodeType === Node.ELEMENT_NODE) {
                // 检查新增的元素及其后代
                const lazyImages = node.matches('img[data-src]')
                    ? [node]
                    : node.querySelectorAll('img[data-src]');
                lazyImages.forEach(img => imgObserver.observe(img));
            }
        }
    }
});

domObserver.observe(document.body, { childList: true, subtree: true });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 14.8 ResizeObserver

监听元素尺寸变化:

const observer = new ResizeObserver((entries) => {
    for (const entry of entries) {
        const { width, height } = entry.contentRect;
        console.log(`${entry.target.id}: ${width}x${height}`);
        
        // borderBoxSize(更精确)
        const borderBox = entry.borderBoxSize[0];
        console.log(`Border box: ${borderBox.inlineSize}x${borderBox.blockSize}`);
    }
});

observer.observe(document.querySelector('.responsive-box'));

// 应用:响应式组件
const container = document.querySelector('.container');
const resizeObserver = new ResizeObserver(entries => {
    const { width } = entries[0].contentRect;
    if (width < 600) {
        container.classList.add('compact');
    } else {
        container.classList.remove('compact');
    }
});
resizeObserver.observe(container);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 14.9 性能优化

# 14.9.1 减少重排(Reflow)

触发重排的操作:修改元素几何属性(width、height、padding、margin)、添加删除元素、读取布局信息(offsetTop、getBoundingClientRect)。

// 不推荐:读写交替触发多次重排
el.style.width = '100px';
const h = el.offsetHeight; // 强制重排
el.style.height = h + 'px';

// 推荐:先批量读,再批量写
const h = el.offsetHeight; // 读
el.style.width = '100px';  // 写
el.style.height = h + 'px'; // 写(合并为一次重排)
1
2
3
4
5
6
7
8
9

# 14.9.2 requestAnimationFrame

DOM 动画应使用 requestAnimationFrame 替代 setInterval:

function animate(element, target) {
    let current = 0;
    
    function step() {
        current += 2;
        element.style.transform = `translateX(${current}px)`;
        
        if (current < target) {
            requestAnimationFrame(step);
        }
    }
    
    requestAnimationFrame(step);
}

animate(document.querySelector('.box'), 300);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 14.9.3 虚拟 DOM 的核心思想

// 虚拟 DOM 是 JavaScript 对象,描述真实 DOM 结构
const vnode = {
    tag: 'div',
    props: { class: 'container' },
    children: [
        { tag: 'p', props: {}, children: ['Hello'] },
        { tag: 'p', props: {}, children: ['World'] },
    ]
};

// 渲染函数:vnode → 真实 DOM
function render(vnode) {
    if (typeof vnode === 'string') {
        return document.createTextNode(vnode);
    }
    
    const el = document.createElement(vnode.tag);
    
    for (const [key, value] of Object.entries(vnode.props || {})) {
        el.setAttribute(key, value);
    }
    
    for (const child of vnode.children || []) {
        el.appendChild(render(child));
    }
    
    return el;
}

// 简化的 diff 算法思路:
// 比较新旧 vnode 树,找出差异,只更新变化的部分
// 这就是 React/Vue 的核心原理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 14.9.4 DOM 操作性能建议

  1. 减少 DOM 访问次数:缓存 DOM 引用和计算结果
  2. 批量修改:使用 DocumentFragment 或 innerHTML
  3. 避免强制同步布局:不要读写交替
  4. 使用 CSS 类切换替代逐个修改 style 属性
  5. 使用事件委托替代大量事件绑定
  6. 使用 transform 和 opacity 做动画(不触发重排)
  7. 使用 will-change 提示浏览器优化
  8. 使用 content-visibility: auto(CSS)跳过屏幕外元素的渲染
上次更新: 2026/06/10, 11:13:41
Symbol
网络请求

← Symbol 网络请求→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式