Hotdry.

Article

Chrome 声明式局部更新:DOM 差异计算与流式渲染性能优化

Chrome 148 引入的 Declarative Partial Updates API 通过处理指令占位符与模板替换机制,实现无需完整页面刷新的流式 DOM 更新,显著降低主线程阻塞与布局抖动。

2026-05-24web

背景:传统 HTML 渲染的瓶颈

现代 Web 应用早已超越静态文档的范畴,但 HTML 的解析与渲染仍遵循严格的上至下顺序。这种线性处理模式导致两个核心问题:一是关键内容必须等待后续非关键资源加载完成后才能渲染;二是动态更新往往需要借助 JavaScript 框架进行复杂的 DOM 操作,引入额外的运行时开销与布局抖动。

Chrome 团队在 2026 年 5 月推出的 Declarative Partial Updates API,旨在通过声明式机制解决上述痛点。该 API 从 Chrome 148 版本开始提供实验性支持,开发者可通过 chrome://flags/#enable-experimental-web-platform-features 启用。其核心设计哲学是:让浏览器原生支持局部 DOM 更新,减少对 JavaScript 框架的依赖,同时保留与现有框架生态的兼容性。

核心机制:处理指令与模板替换

该 API 引入了一套基于 Processing Instruction(处理指令)的占位符系统。处理指令在 XML 中早已存在,但在 HTML 中 traditionally 被视为注释忽略。新 API 改变了这一行为,使其成为可引用的 DOM 锚点。

基础替换模式

最简使用场景涉及 <?marker> 指令与 <template for> 元素的配合:

<div>
  <?marker name="placeholder">
</div>

<template for="placeholder">
  Here is some <em>HTML content</em>!
</template>

浏览器解析后,DOM 结构将变为:

<div>
  Here is some <em>HTML content</em>!
</div>

这里的差异计算逻辑由浏览器在解析层完成,而非通过 JavaScript 对比虚拟 DOM。这意味着:无需构建完整的 DOM 树即可确定更新范围,显著降低内存占用与计算成本。

范围标记与渐进加载

针对需要显示加载状态的场景,API 提供 <?start><?end> 范围标记:

<div>
  <?start name="results">
  Loading…
  <?end>
</div>

<template for="results">
  <li>Result One</li>
  <li>Result Two</li>
</template>

在模板到达前,"Loading…" 作为临时内容呈现;模板解析完成后,范围内的内容被原子性替换。这种机制天然支持流式 HTML 传输—— 服务器可先发送页面骨架与占位符,随后按需流式推送各区块内容。

性能优化策略

1. 避免完整重排

传统 SPA 路由切换或数据更新时,框架通常需要重新渲染整个组件子树,触发级联的布局计算与样式重算。Declarative Partial Updates 的 DOM 差异计算在解析阶段完成,仅更新被标记的范围,其余 DOM 节点保持引用稳定。这带来两个直接收益:

  • 布局抖动(Layout Thrashing)减少:未变更元素的几何信息无需重新计算
  • 样式重算范围缩小:CSS 选择器匹配仅限于更新区域

2. 支持 Island 架构

Astro 等框架推广的 Island 架构(交互式岛屿嵌入静态 HTML)与该 API 高度契合。静态内容可直接在 HTML 中声明,动态部分通过 <?marker> 占位,JavaScript 仅负责激活交互岛屿,而非管理整个渲染流程。

3. 流式传输优化

API 允许内容按就绪顺序而非文档顺序传输。例如,首屏关键 CSS 与 HTML 可优先发送,大型导航菜单(mega menu)等低优先级内容延迟至文档末尾流式插入,通过 <?marker> 回填至正确位置。这优化了 ** 时间到首次内容绘制(FCP)最大内容绘制(LCP)** 指标。

JavaScript API:一致的插入与流式接口

除声明式 HTML 机制外,Chrome 还提供配套的 JavaScript API,解决现有 DOM 插入方法(innerHTMLinsertAdjacentHTMLsetHTML 等)命名不一致、行为差异大的问题。

API 矩阵

操作 静态版本 流式版本
设置元素 HTML setHTML(html, options) streamHTML(options)
替换整个元素 replaceWithHTML(html, options) streamReplaceWithHTML(options)
元素前插入 beforeHTML(html, options) streamBeforeHTML(options)
元素首子位置插入 prependHTML(html, options) streamPrependHTML(options)
元素末子位置插入 appendHTML(html, options) streamAppendHTML(options)
元素后插入 afterHTML(html, options) streamAfterHTML(options)

流式使用示例

流式版本与 Streams API 集成,支持从 Fetch 响应直接管道传输:

const contentElement = document.querySelector('#content');
const response = await fetch('/api/content.html');

response.body
  .pipeThrough(new TextDecoderStream())
  .pipeTo(contentElement.streamHTMLUnsafe());

这种模式下,HTML 片段到达即解析渲染,无需等待完整响应。对于大型内容更新,可显著降低 ** 时间到可交互(TTI)** 延迟。

安全边界

所有 API 提供 SafeUnsafe 变体:

  • Safe 版本:默认启用 Sanitizer,自动转义危险标签(如 <script>
  • Unsafe 版本:允许通过 runScripts: true 执行脚本,适用于可信内容

这种显式命名策略强制开发者思考安全风险,而非依赖隐式默认行为。

限制与注意事项

安全约束

<template for> 只能更新同父元素内的处理指令。将模板直接置于 <body> 下可访问整个文档(包括 <head>),但跨父级更新被禁止以防止意外越权修改。

动态插入的边界情况

通过 setHTMLinnerHTML 动态插入 <template for> 时,由于解析发生在临时文档片段中,无法修改已存在的 DOM。只有使用 streamHTMLUnsafe 等流式方法时,模板才能直接替换现有内容。

元素移动风险

若在 <template for> 已开始流式传输后移动目标处理指令,新内容将继续流入原位置,可能导致意外渲染结果。生产环境中应确保占位符位置稳定。

未来演进方向

WICG 草案中提及的潜在扩展包括:

  • 客户端包含(Client Side Includes)<template for="footer" patchsrc="/partials/footer.html"> 支持从外部 URL 流式获取片段
  • 批量更新(Batching):确保多个模板更新在同一帧内原子性应用,减少中间状态的布局计算
  • 内容版本控制:通过修订号防止已渲染内容的重复覆盖,保留用户状态(如滚动位置、表单输入)
  • 补丁级 Sanitization<template for="icon" safe> 允许在替换过程中应用细粒度安全策略

落地建议

对于希望尝鲜的团队,建议采取以下渐进策略:

  1. 评估场景:优先在内容流式加载(如搜索结果、动态列表)与 Island 架构场景试点
  2. Polyfill 兜底:Chrome 团队提供了 template-for-polyfillhtml-setters-polyfill,可在不支持的原生环境中模拟 API 行为(注:Polyfill 无法真正流式解析,仅缓冲后批量应用)
  3. 性能监控:关注 FCP、LCP、CLS 核心指标变化,验证局部更新是否真正减少布局抖动
  4. 安全审计:使用 Unsafe API 时,确保输入来源可信或配合自定义 Sanitizer 配置

参考资料

web

内容声明:本文无广告投放、无付费植入。

如有事实性问题,欢迎发送勘误至 i@hotdrydog.com