Chrome 155 把 insertAdjacentHTML 一大家子全换了:七个新方法加流式插入,HTML 终于能边下边拼进 DOM

insertAdjacentHTML 这个用了二十多年、参数靠字符串位置来猜的 API,Chrome 155 准备把它连同 innerHTML、setHTML 一大家子一起换掉:七个新方法,再加一套流式插入,HTML 终于可以边下载边拼进 DOM,而不是先攒成完整字符串再一次性塞进去。

先把结论说了:Chrome 155 计划发布的新 API 矩阵,是过去那些 DOM 插入方法第一次被”按动作命名”重新梳理。以前你面对的是 innerHTML、outerHTML、insertAdjacentHTML、createContextualFragment、setHTMLUnsafe 五个名字,每个的转义规则、是否执行脚本、和 Trusted Types 怎么配合都不一样——很少有人能诚实地逐条答上来。现在换成一件事一个名字,静态版和流式版一一对应。

问题:五个 API,五套心智模型

insertAdjacentHTML 最典型:你得记住第二个参数是 'beforebegin'、'afterbegin'、'beforeend'、'afterend' 这四选一的字符串,还得记住它不转义、会执行脚本还是不会、和 Trusted Types 又是怎么互动的。同事 review 时看到 'afterbegin' 经常要停下来想一秒。

更致命的限制是:这些方法全都只能处理”已经完整的 HTML 字符串”。也就是说,响应得先整段下载完,才能开始插入。但 HTML 本来的优势就是流式——内容一到就能立刻渲染。过去想绕过这点,要么手动切分 payload,要么用已经过时的 document.write,两个都会引入新问题。

方案:一套按动作命名的方法 + 流式版本

Chrome 从 148 起在 chrome://flags/#enable-experimental-web-platform-features 后面可以试,计划在 155 正式发布。核心矩阵长这样:

操作 静态版 流式版
设置元素内容 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)

看名字就知道干什么,这个命名一致性本身就是这次改动的最大价值。静态版收一个 HTML 字符串加可选 options;流式版返回一个 WritableStream,可以拿 getWriter() 手动写,也可以直接从 fetch 的响应管道接进来。

流式插入怎么用

以前要把一段流式 HTML 接进 DOM,绕得不行。现在直接管道:

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

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

还加了个 textStream() 便捷方法,省掉中间的 TextDecoderStream 那一步:

const response = await fetch('/api/content.html');
response.textStream().pipeTo(content.streamHTMLUnsafe());

或者手动写流、想写多久写多久:

const writer = content.streamHTMLUnsafe().getWriter();
while (true) {
  await writer.write(`<p>${++i}</p>`);
  await new Promise(r => setTimeout(r, 1000));
}
writer.close();

片段一到就解析渲染,不用等整个响应结束。对大型内容更新,这能明显压低 TTFB 之后到可交互之间的那段延迟。

安全模型是内建的,不是可选的

每个方法都有 Unsafe 版本(setHTMLUnsafe、streamHTMLUnsafe 等),区别在于:默认版走 Sanitizer,会先把危险 HTML 清掉;Unsafe 版不清洗、可以执行脚本。options 里还能传自定义 sanitizer:

const basicSanitizer = new Sanitizer({ elements: ['em', 'i', 'b', 'strong'] });
content.setHTML('<p>hi</em>', { sanitizer: basicSanitizer });

这比 innerHTML 直接赋值安全得多——innerHTML 不提供任何清洗。把”用哪个安全级别”从”你记不记得住这个方法默认行为”变成”你选哪个方法名”,这是这套 API 真正的工程价值。

配套的还有:无序流式传输

同一批工作还有一个更激进的机制——用处理指令占位符加带 for 属性的 <template>,让 HTML 可以乱序到达、原位回填:

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

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

解析后占位符被模板内容替换。还有 <?start> / <?end> 范围标记,可以先用 “Loading…” 占位,模板到了再替换。这条链路的头部(首屏关键 CSS 和 HTML)可以先发,大型导航菜单这种低优先级内容放到文档末尾流式回填,FCP 和 LCP 都能受益。这块目前还没在所有浏览器落地,但 Chrome 团队已经放了 template-for-polyfill 到 npm,现在就能用。

给你的下一步

  1. 别现在改生产代码。静态 + 流式这套计划在 Chrome 155 发布,其他浏览器还没跟上。先在 Chrome 148+ 的开发版打开 chrome://flags/#enable-experimental-web-platform-features 试手感。
  2. 把 insertAdjacentHTML 的调用点列出来。新方法名和参数一一对应,迁移基本是机械替换,趁现在盘点比发布后慌着改强。
  3. 流式插入优先用在 AI 场景。聊天流式响应、SSE 推来的 HTML 片段,直接用 textStream().pipeTo(streamHTMLUnsafe()),比现在手动切字符串拼 DOM 干净得多。
  4. 默认用带 Sanitizer 的版本,只有在内容完全可控时才用 Unsafe 版——命名本身就在提醒你这一点。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 616 阅读