news 2026/9/25 5:10:59

DOM核心知识全解:从文档对象模型到虚拟DOM与事件机制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
DOM核心知识全解:从文档对象模型到虚拟DOM与事件机制

当你知道的越多,就越发现 DOM 这个看起来简单的东西,其实是整个前端世界的地基。很多人在初学阶段把 DOM 当作“用 JS 改网页内容的工具”,等面试被问到“虚拟 DOM 和 diff 算法”“事件冒泡、事件捕获、事件委托”时,才发现自己连 DOM 的边界都没划清。这篇文章我准备一次性讲透,把 DOM 从是什么、怎么构建,到怎么渲染、怎么操作,再到虚拟 DOM 和事件机制这些高频考点全部串起来,适合刚入门的前端,也适合准备面试但想系统复盘一遍的同学。

先交代一句:DOM 全称叫 Document Object Model,中文是“文档对象模型”。它不是一门语言,也不是浏览器的某个具体功能模块,而是浏览器暴露给脚本的一棵可操作的内存结构树。你写的 HTML 只是文本,真正让 JavaScript 能“看见”页面元素并修改它们,靠的全是这棵 DOM 树。

1. 拆开缩写的三个词:文档、对象、模型,凭什么组成一门技术

1.1 文档、对象、模型三个词到底是什么意思

很多人只记住了“DOM 是操作页面的接口”,但这三个词其实精准描述了它存在的方式。

“文档”指的是 HTML 文档本身,也就是你在编辑器里写的那一串标签文本。浏览器拿到这串文本之后,会把它解析成结构化的数据。注意这里的“解析”很关键,因为文本本身没有任何结构化能力,<div>和<span>谁是父子关系,必须要通过语法分析才能确定。

“对象”指的是内存中的一个个节点实例。每个 HTML 标签、文本、注释、属性,在 DOM 里都是一个“对象”,这些对象有自己的属性和方法。比如一个<div>标签,在 DOM 中是HTMLDivElement的实例,它继承了HTMLElement、Element、Node这些原型链上的能力,所以你才能调用appendChild()、querySelector()这类方法。

“模型”指的是整个文档的结构被抽象成了一种“树模型”,也就是节点之间的关系。树的根是document,往下依次是html、head、body,再往下一层层展开。DOM 提供了一整套对这个树模型进行读取、插入、删除、替换的 API,浏览器会根据这棵树的实时状态决定要不要重新渲染。

一句话总结:DOM 是 HTML 文档在内存里的一种树形对象表示,它既是数据的结构,也是操作的入口。没有 DOM,JavaScript 就是一门和网页毫无关系的语言。

1.2 浏览器是怎么把 HTML 变成 DOM 树的

这个过程值得仔细讲,因为它决定了你对“网页渲染”的很多判断。

浏览器拿到 HTML 字节流之后,第一步是字节解码,把网络传输过来的字节按编码规则转成字符,比如 UTF-8 解码成可读的文本。第二步是词法分析,也叫 tokenization,把字符串拆成一个一个的 token,比如开始标签<div>、属性class="box"、文本内容、结束标签</div>,这些 token 都有类型,标记着它们在文档中的角色。第三步是语法分析,也就是构建节点树,浏览器按照 HTML 规范里定义的规则,把 token 组装成嵌套的节点。

这里有个很有意思的细节:HTML 解析并不是“洗干净之后一次性建树”,而是一个边读边建的过程。浏览器维护着一个“解析器状态机”,读到开始标签就创建一个元素节点并压入栈,读到文本就追加到当前节点的文本内容里,读到结束标签就出栈。碰到标签嵌套错误,比如<p><div></p></div>,解析器不会直接报错,而是按照规范里的“错误处理规则”去修正,自动补全或者重组结构。

原因在于 HTML 是一个“宽容”的语言。浏览器厂商设计解析器时的共识是:网页不能被语法错误彻底卡死,能修就修,保证用户永远都能看到内容。所以你在浏览器里看到的 DOM 结构,未必和你编辑器里写的源码一字不差,它已经是“修正优化”过的一版。

1.3 DOM 和 HTML、CSSOM 的关系别再混为一谈

初学者最容易搞混的一件事,是把“HTML 文本”直接当成“DOM”。其实二者是源数据与结构化表达的关系。HTML 是静态的,刷新之后它会重新解析一遍;DOM 是动态的,JS 改了它,页面上的内容立刻变化,但这个变化并不会回写到 HTML 源文件里,刷新页面之后又恢复原样。

还需要分清 DOM 不是仅有的“对象模型”,浏览器里还有一个和它并列的结构叫 CSSOM,也就是 CSS 对象模型。CSSOM 是浏览器解析 CSS 文本生成的样式规则树,它负责回答“这个元素最终该长什么样”。渲染时,浏览器会把 DOM 和 CSSOM 合并成渲染树,只有渲染树上能够影响视觉布局的节点才会被真正绘制。display: none的节点会从渲染树里被剔除,但它依然在 DOM 树里。这就是为什么getElementById依然能找到那个元素,只是因为样式隐藏所以看不见而已。

我把 DOM、CSSOM、渲染树三者的关系用一张表对给你:

结构来源职责是否直接决定视觉
DOMHTML 解析描述页面结构和内容间接
CSSOMCSS 解析描述样式规则和层叠关系间接
Render TreeDOM 与 CSSOM 合并提供布局与绘制的节点集合直接

理解了它们之间的差别,后面的渲染性能问题就很好解释了。

2. 浏览器渲染流程与“从上到下”顺序渲染的真相

2.1 从 HTML 到界面,浏览器到底做了几次工

渲染流程可以概括为五个大阶段:解析、样式计算、布局、绘制、合成。

解析阶段生成了 DOM 和 CSSOM,这一点前面已经说过。样式计算阶段,浏览器会根据 CSSOM 的规则,为 DOM 树里的每个可见节点计算出最终的样式值,这个阶段会处理继承、层叠、单位换算,比如把em、vh等相对单位换算成像素。布局阶段,也叫 reflow,浏览器会计算每个元素在页面上的几何位置和尺寸,包括它相对于父容器和兄弟节点的偏移量。绘制阶段,把每个节点的背景、边框、文字、阴影等视觉属性绘制到多个图层上。合成阶段,浏览器把各个图层按正确的层级顺序交给 GPU 合成,最终显示在屏幕上。

这里有一个重要判断:上面说的是“一次完整渲染”的流程,但真实页面的首次加载往往不是一次性的。HTML 是边解析边构建 DOM 的,只要解析器先读完了<head>里的内容,并且 CSS 也没有阻塞,浏览器就可能先绘制出第一屏的内容,然后继续解析主体部分。这种机制叫做渐进式渲染,是浏览器特意为了“最早时间看到内容”而设计的。

2.2 “DOM 从上到下顺序渲染是不是更快”?答案没那么简单

很多开发者问:DOM 从上到下按顺序渲染,是不是就更快?这个说法有一定道理,但远没有“顺序决定一切”那么绝对。

说它有道理,是因为浏览器的 HTML 解析器确实是按照字节流的顺序,从上到下扫描、解析、构建节点的。按理说,HTML 顶部的内容总是会先被解析到,理论上也会先出现在渲染结果里。但真实情况受两个关键因素影响:CSS 和 JavaScript。

CSS 会阻塞渲染。浏览器只有在解析完样式表之后才能进行样式计算,所以你如果把<style>或者外部样式表放在页面底部,浏览器会先解析出一部分 DOM,却发现没有样式规则可以做计算,最终在布局之前停下来等待。等到 CSS 到了,它再重新计算。这反而拖慢了首屏速度。因此业界惯例是把 CSS 放在<head>里,让样式尽早就位。

JavaScript 会阻塞解析。当解析器碰到<script>标签时,按照早期 HTML 规范,它必须停下来等脚本执行完,因为脚本里可能调用了document.write()来修改文档流。阻塞期间,DOM 构建暂停,渲染也就停住。为此,我们才把普通脚本放在</body>前,或者给脚本加defer和async。

回到“从上到下顺序渲染是不是更快”这个问题,比较准确的回答是:从上到下顺序解析确实让渐进式渲染成为可能,看起来是“边读边画”,但实际速度取决于有多少渲染阻塞资源。顺序本身不是性能瓶颈,瓶颈在于你的 CSS 和脚本放置策略是否让解析器“空转等待”。

2.3 回流与重绘:为什么操作 DOM 慢,就慢在这里

前端圈子里有一句老话:操作 DOM 是最贵的操作。贵就贵在当你修改 DOM 时,浏览器可能被迫重新执行“布局”甚至“绘制”。

回流(Reflow)指的是布局阶段被重新触发。当你改变元素的尺寸、位置、内容、字体大小,或者增删节点时,浏览器需要重新计算受影响区域里所有元素的几何属性。最糟糕的情况是修改了根节点附近的结构,整个页面都重新布局。比如改body的宽度,所有子元素的排列都要重新算一遍。

重绘(Repaint)指的是绘制阶段被重新触发。当你只修改颜色、背景、阴影这些不影响布局的属性时,浏览器不需要重新算几何,只需要把像素重新画一遍。重绘的代价虽然比回流小,但也不是免费的,因为浏览器还是要遍历图层、计算绘制指令。

在实际开发里,常见的“慢”是回流和重绘交叠出现。比如你用一个循环去逐个插入列表项,每插入一项都可能触发一次回流,几十上百次循环下来性能直接崩。解决思路很清晰:把 DOM 操作合并,减少触发次数,我推荐以下几种做法:

  • 批量读取再批量写入,不要在读写之间交替触发布局。
  • 使用DocumentFragment先构建子节点,再一次性插入。
  • 用display: none先隐藏元素,操作完再显示,只触发两次回流,而不是 N 次。
  • 动画里优先用transform和opacity,因为它们能走合成层,不触发布局和绘制。

2.4 一套可以直接落地的渲染性能优化清单

这部分是我在实际项目中整理出来的,一般项目照着做都不会差:

  1. 把 CSS 放在<head>,保证样式尽早参与渲染。
  2. 把 JS 放在</body>前,或使用defer/async避免阻塞。
  3. 减少 DOM 嵌套层级,特别是深层无序的嵌套,会拖慢样式计算速度。
  4. 用classList切换多个样式类,不要一条一条地改style属性。
  5. 避免频繁读取offsetWidth、offsetHeight、getComputedStyle等布局属性,它们在读取时会强制刷新布局队列。
  6. 利用事件委托,减少事件监听器的数量。
  7. 数据列表用虚拟滚动,只渲染可见区域,控制 DOM 节点总量。

这些优化背后其实都是同一个原则:让浏览器少干活。DOM 操作本身不慢,慢的是你可能不小心让浏览器重复干了很多不必要的活。

3. 日常开发里高频用到的 DOM 操作技法

3.1 一张表看懂查、增、改、删四类操作

DOM 提供的 API 非常多,但日常开发真正高频用到的不外乎“查、增、改、删”四类。我把它们整理成表,顺手标注了每个方法的特点和使用场景。

类型常用 API说明注意点
查询document.getElementById()按 id 找单个元素老牌 API,性能极佳
查询document.querySelector()按 CSS 选择器找单个元素方便但比 getElementById 慢
查询document.querySelectorAll()按选择器找一组元素返回静态 NodeList
查询element.closest()从自身向上找符合选择器的祖先事件委托里的神器
新增document.createElement()创建元素节点需配合 append 使用
新增element.insertAdjacentHTML()在指定位置插入 HTML慎用,涉及 XSS 风险
新增element.append()/prepend()尾部/头部添加子节点支持多节点和字符串
修改element.textContent设置或读取文本内容设置时自动转义标签
修改element.setAttribute()设置属性可直接赋值部分属性
修改element.classList操作 class比操作 className 更方便
删除element.remove()直接移除元素兼容性没问题
删除element.replaceWith()把当前元素替换成另一个节点一步到位,避免手动删再插

一个值得强调的点是textContent和innerHTML的区别。textContent会把内容当作纯文本插入,标签会被自动转义成普通文本;innerHTML则会把字符串当作 HTML 解析。前者安全,后者在数据可控的情况下才建议用。

3.2 批量插入的正确姿势:DocumentFragment 与一次性重绘

如果你需要在页面里添加一个包含 100 个列表项的<ul>,最糟糕的写法是循环 100 次,每次都创建节点并appendChild,这样触发的回流次数可能上百次。正确的姿势是利用DocumentFragment做一个“离线容器”。

const ul = document.querySelector('#list'); const fragment = document.createDocumentFragment(); for (let i = 0; i < 100; i++) { const li = document.createElement('li'); li.textContent = `第 ${i + 1} 项`; fragment.appendChild(li); } ul.appendChild(fragment);

DocumentFragment是一种特殊的节点,它可以临时存储子节点,但不会成为真实 DOM 的一部分,因此你对它的所有修改都不会触发回流。当你把它一次性塞进ul时,浏览器只执行一次插入操作,DOM 更新也就只触发一次回流。实测下来,这种写法在渲染上百条数据时性能提升非常明显。

这里要住的细节是:fragment.appendChild(li)之后,li会暂时从 fragment 里“脱离”,但当你把fragment插入ul时,fragment里的所有子节点会自动转移到ul下,fragment自己则变回空状态。这是大部分前端都不太注意,面试里偶尔会被问到的一个小考点。

3.3 动画场景里绕开“布局抖动”的实战技巧

我自己在写复杂动画时踩过好几次坑,最典型的是“布局抖动”引发的掉帧。布局抖动指的是:你在同一个函数里既读取了布局属性,又修改了 DOM 属性,导致浏览器被迫反复执行同步布局。

举个例子:

// 问题代码:读写交替,触发多次回流 for (let i = 0; i < items.length; i++) { const width = items[i].offsetWidth; // 读取 items[i].style.width = width + 10 + 'px'; // 写入 }

正确做法是把读取和写入分成两个阶段。先读所有需要的布局数据,存到数组里,再统一写入。

// 优化代码:先读后写 const widths = []; for (let i = 0; i < items.length; i++) { widths.push(items[i].offsetWidth); } for (let i = 0; i < items.length; i++) { items[i].style.width = widths[i] + 10 + 'px'; }

动画循环里也是一样的道理。如果你在requestAnimationFrame回调里既读取又写入,浏览器没办法把多个帧的布局工作合并起来,动画就卡顿。更推荐的做法是:用transform来实现位移和缩放动画,因为 transform 只触发合成层的更新,走 GPU 加速,完全绕开布局和绘制阶段。

4. 事件模型:冒泡、捕获、委托一网打尽

4.1 DOM 事件流的三个阶段,别再只背一个“捕获”

几乎每个前端面试都会问到事件流。完整的事件流分为三个阶段:捕获阶段、目标阶段、冒泡阶段。

你点击页面上一个按钮时,事件的完整传播路径是这样的:从window出发,经过document,再到html,然后是body,一路向下到目标元素的外层容器,最终到达目标元素本身。这个“从根到目标”的下行过程就是捕获阶段。命中目标元素的那一刻,事件处于目标阶段,事件监听器开始在目标上执行。紧接着,事件沿原路返回——从目标元素一层层向上传播——这个过程就是冒泡阶段。

给你看一个直观的例子。假设结构是body > div > button,你点了button,事件传播顺序是:

window -> document -> html -> body -> div -> button(目标阶段) -> div -> body -> html -> document -> window

addEventListener的第三个参数控制监听器在哪个阶段触发。默认是false,表示监听器在冒泡阶段触发;传true表示在捕获阶段触发。

const div = document.querySelector('div'); // 冒泡阶段触发 div.addEventListener('click', () => { console.log('冒泡阶段'); }, false); // 捕获阶段触发 div.addEventListener('click', () => { console.log('捕获阶段'); }, true);

面试的时候,能够完整说出这两个阶段的区别,并且清楚第三个参数的默认值,就比大部分只会说“事件冒泡”的候选人强很多。

4.2 冒泡与捕获的实际差异:从代码出发理解

写页面的时候,为什么我们常常对“冒泡”讨论得更多?因为实际开发里绝大多数监听器都挂在冒泡阶段,它的行为更符合直觉:事件从目标往上走,父容器能统一收到所有子元素的事件。

捕获阶段用得少,但它在某些场景下很关键。比如你想在所有子元素之前拦截某个事件,就应该在捕获阶段监听。一个常见需求:页面里有一个列表,你希望点击任何区域的空白处都收起菜单,但不希望点击菜单本身时也触发收起。你可以在document的捕获阶段判断目标是不是菜单内部的元素,提前决定要不要阻止它。

document.addEventListener('click', (e) => { if (!menu.contains(e.target)) { menu.hide(); } }, true);

这里第二个参数true必不可少。因为你如果写在冒泡阶段,事件里那些子元素上的监听器可能已经把数据修改掉了,或者已经执行了某些业务逻辑。捕获阶段监听能让你的“兜底逻辑”最早介入。

顺带提一下两个容易混的方法:

  • stopPropagation():阻止事件继续传播,冒泡阶段被调用后,父元素不会再收到该事件。
  • stopImmediatePropagation():不仅阻止传播,还阻止当前元素上其他监听器的执行。
  • preventDefault():阻止浏览器默认行为,比如阻止跳转链接,但它不会阻止事件传播。

三者的边界一定要分清楚,面试时会被追问。

4.3 事件委托:最经典也最常被问到的性能技巧

事件委托的核心思路其实很简单:因为事件有冒泡机制,子元素的点击事件会冒泡到父元素,所以我们可以只在父元素上绑定一个监听器,通过判断event.target来处理任意一个子元素的事件。

经典场景是动态列表:列表项由接口返回,用户也能随时新增项,如果给每一项都绑定监听器,新增时还得重新绑定,非常麻烦。事件委托只需要在容器上绑定一次:

document.querySelector('#list').addEventListener('click', (e) => { const item = e.target.closest('.list-item'); if (!item) return; item.classList.toggle('selected'); });

这段代码里有几个关键点。第一,e.target是真正被点击的元素,可能是.list-item里的某个子元素,所以要用closest()从它自己向上找到.list-item这个祖先。第二,closest()找不到时返回null,所以要提前return掉。第三,事件委托天然覆盖“未来新增的节点”,这是它最值钱的地方。

面试时如果被问到“事件委托的优点”,你可以这样答:减少内存占用,因为监听器数量少;提高绑定效率,一次性绑定代替大量循环;支持动态节点,新增元素不需要重复绑定。如果被追问“缺点”,也值得说清楚,事件委托依赖事件冒泡,focus、blur这类不冒泡的事件没法直接委托,但可以用focusin、focusout代替。

5. 虚拟 DOM 与 diff 算法:面试高频考点拆解

5.1 虚拟 DOM 到底是虚拟了什么

很多面试者把虚拟 DOM 背成“用 JS 对象模拟 DOM”,但为什么需要这样一个模拟结构,真的理解的人不多。

核心原因是:直接操作真实 DOM 代价高。真实 DOM 节点包含大量的属性,一个普通的<div>节点上可能有几十上百个内置属性和方法,频繁创建、比较、更新它们是很重的。而虚拟 DOM 用一个极简的 JS 对象描述节点信息,它只记录标签名、属性、子节点等关键数据,相比真实节点轻得多。

// 一个最简单的虚拟 DOM 节点结构 const vnode = { tag: 'div', props: { id: 'app', class: 'container' }, children: [ { tag: 'h1', props: {}, children: ['标题'] } ] };

渲染流程是这样的:组件运行 render 函数,生成新的虚拟 DOM 树;框架把新树和旧树作 diff 比较,计算出差异集合;框架只把差异同步到真实 DOM 上,而不是直接重建整棵树。这样一来,即使页面发生大量状态变化,真实 DOM 的操作次数也被压到了最小。

虚拟 DOM 最大的价值其实不是“比手写 DOM 操作更快”,而是它把“描述页面状态”和“更新真实 DOM”这两个过程解耦了。开发者只需要描述页面“应该长什么样”,至于怎么高效地更新,交给框架处理。这种抽象让跨平台成为可能,因为你可以在不同平台上用同一套描述逻辑,渲染到 DOM、Canvas、甚至原生组件。

5.2 Diff 算法核心:用“最小差异”换可接受性能

diff 算法要解决的问题是:给定两棵虚拟 DOM 树,如何找到它们之间最小的更新差异?

理论上,两棵树的最小差异比较是一个非常复杂的算法,时间复杂度会高到不可接受。所以 Vue 和 React 都采取了一个“牺牲理论最优、换取实际可用”的策略:只在同一层级做比较,不跨层级移动节点。这基于一个实践中几乎总是成立的假设:DOM 节点不会无端地从一处被移到另一处,跨层级的操作是极少数。

在同层比较时,框架会逐个对比新旧节点的tag和props。如果节点类型相同,就继续深入地比较子节点;如果节点类型不同,直接把旧节点整个替换掉,不再花时间比较它的子树。这种“同层比较 + 类型不同即替换”的规则,把 diff 的时间复杂度压到了接近 O(n)。

为了让重复节点能被复用,框架引入了key。key是节点在列表中的唯一标识,当新旧列表结构相似但顺序发生变化时,diff 算法可以通过key判断出哪些节点是“同一个”,然后执行移动而不是删除重建。这就是为什么在 Vue 的v-for列表里,官方一直要求你绑定一个稳定唯一的key,而不要用数组索引。

5.3 React 和 Vue 的 diff 到底差在哪

这个问题经常在面试中出现。从宏观上讲,两者的思路一致,都是同层比较 + key 优化,但微观细节有明显差异。

Vue 2 的 diff 采用的是“双端比较”策略。它会同时从新旧两个数组的两端开始,尝试用头部、尾部节点做比较,优先处理“头头相同、尾尾相同、头尾相同”这几种常见情况,把能复用的节点尽量找出来。这种策略在列表只是末尾新增项、头部新增项等场景里有明显优势,能够用最少的移动次数完成更新。

React 从较新的版本开始,diff 的实现往“Fiber 架构”演化。它允许 diff 过程被拆分成多个小任务,按优先级分片执行,而不是一次性同步完成,这样浏览器可以在 diff 过程中穿插处理用户输入和动画,不至于因为长时间占用主线程而卡顿。

如果你在面试时能说出这种层次差异,就能给面试官留下“真读过源码级内容”的印象。但要记住,面试官最关心的不是你能背多少细节,而是你有没有理解虚拟 DOM 解决的问题:通过最小化真实 DOM 操作,让开发者的心智负担和框架的性能表现达成平衡。

6. 真实的 DOM 世界:安全、歧义与面试清单

6.1 DOM 型 XSS:不是网上随便说说那么简单

“DOM 型 XSS”是 Web 安全领域的一个老话题,和 DOM 有直接关系。它的原理是:前端代码将不可信的外部数据直接插入到 DOM 中,导致恶意脚本在页面上下文里被解析和执行。

最常见的场景是使用innerHTML:

const name = new URLSearchParams(window.location.search).get('name'); document.querySelector('#welcome').innerHTML = `欢迎回来,${name}`;

如果攻击者构造一个这样的链接:

https://example.com/?name=<img src=x onerror=alert(document.cookie)>

那么innerHTML会把这个字符串当作 HTML 解析,img标签被创建,onerror里的脚本自动执行,alert(document.cookie)弹出。这只是一个演示,真正的攻击者可以窃取 Cookie、修改页面内容、伪装成登录框骗取密码。

防御的核心原则是:不要用 HTML 解析器处理不可信数据。登一条入textContent或innerText代替innerHTML,数据就会被当作纯文本插入,标签失去效果。此外,服务端对输入做严格的校验和编码,响应头配置正确的 Content-Security-Policy,都能有效降低 DOM 型 XSS 的风险。

面试时被问到 XSS,能结合 DOM 操作讲清楚攻击链路,并且主动说出textContent和innerHTML的本质区别,就已经能体现你对 Web 安全的认知深度。

6.2 别被同名坑了:ArcMap、OSGB 里的“DOM”是另一回事

这里我必须插个题外话,因为在测绘和地理信息行业里,“DOM”是一个被我很多朋友问过太多遍的同名缩写。

在这个领域中,DOM 指 Digital Orthophoto Model,即数字正射影像模型,它和前端文档对象模型完全不是一个东西。比如有人在用 ArcMap 10.8 给影像数据做切片、或者做本地切片时,会搜索“DOM 切片”,得到的自然是地理信息领域的结果。那里的 DOM 是指经过几何校正后、带有真实地理坐标的航空或卫星影像,用来在地图软件里做底图或叠加分析,和浏览器、JavaScript、HTML 没任何关系。

还有一个常见混淆是 OSGB 和 DOM 的关系。OSGB 是 OpenSceneGraph Binary 的缩写,是倾斜摄影测量中生成的三维模型格式,带有完整的几何结构、纹理和层级细节;而测绘领域的 DOM 是二维正射影像,两者是不同类型的数据。在三维测图的工程流程里,通常会把倾斜摄影生成的 OSGB 模型和正射影像 DOM 配合使用,模型负责立体真实的城市面貌,影像负责平面精度和纹理参考。它们是互补关系,不是同一个东西。

作为一个前端开发者,碰到这种同名缩写,最好先确认语境再动手查资料,否则很容易被搜索引擎带偏。

6.3 我总结的 DOM 面试复盘清单

最后给准备面试的同学整理一份可以直接对着自测的清单。你能把下面这些问题不看资料回答出来,DOM 这块基本就过关了:

  • 说一说 DOM 全称,以及文档、对象、模型分别代表什么?
  • 浏览器是如何从 HTML 字节流构建 DOM 树的?
  • DOM 和 CSSOM、Render Tree 之间是什么关系?
  • CSS 为什么放在<head>,JS 为什么放在</body>前?
  • 回流和重绘有什么区别?哪些属性会触发回流?
  • 为什么说频繁操作 DOM 很慢?你有哪些减少回流的经验?
  • 事件流分哪三个阶段?addEventListener的第三个参数有什么作用?
  • 事件委托的原理是什么?为什么支持动态节点?
  • 虚拟 DOM 解决了什么问题?它的结构长什么样?
  • diff 算法为什么只比较同层?key的作用是什么?
  • Vue 双端比较和 React Fiber 架构有什么本质区别?
  • 什么是 DOM 型 XSS?如何防御?

这些问题大多数都能在这篇文章里找到答案。如果还有含糊的地方,建议把对应小节重读一遍,然后亲手上手写几个 demo 验证一下。

最后再说一点我的个人体会。DOM 这东西,你可以用 jQuery 或者 Vue、React 很多年都用不到深层原理,但它始终是面试的试金石,也是排查页面性能问题时最终定位的底层逻辑。我见过太多候选人把“虚拟 DOM 比真实 DOM 快”这句话挂在嘴边,但一问到回流重绘、事件流阶段,就立刻露出破绽。别做那种“背结论、不会推导”的人。真正理解了浏览器解析、渲染、事件传播这条链路之后,你会发现所有前端框架的设计都是围绕着 DOM 的高昂代价展开的。这份理解,值得你收藏起来反复看。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 5:07:20

Redis(事务、持久化)

1、redis简介redis是NoSQL&#xff08;Not Only SQL&#xff09;:非关系型数据库常见的非关系型数据库有&#xff1a;MongoDB、Redis、Memcache。Redis是一个开源的使用ANSIC语言编写、支持网络、可基于内存、持久化的日志型、Key-Value数据库&#xff0c;并提供多种语言的API。…

作者头像 李华
网站建设 2026/9/25 5:05:39

基于MatlabSimulin的微电网模型及光伏电池建模仿真分析

目录 第一章 微电网 1 1.1概念 1 1.2技术应用 1 1.3 研究意义 1 第二章 微电网组成元件 2 2.1 开关器件 2 2.1.1 断路器 2 2.1.2 静态开关 2 2.2 分布式电源 3 2.3 储能单元 3 2.4 电力电子接口电路 4 第三章 微电网控制及逆变器原理 4 3.1 微电网的基本结构 4 3.2 微电网的系统…

作者头像 李华