应使用 template 标签预编译结构,配合 DocumentFragment 批量插入,避免 innerHTML 拼接和重复 DOM 重建;需 cloneNode(true)、拆分深层嵌套模板,并警惕 SSR 注入的隐性深度。

用 template 标签预编译结构,避免运行时拼接字符串
直接 innerHTML += '<div>...' 不仅易 XSS,还会触发多次 DOM 重建和样式重计算。浏览器对 <code>template 的内容不做解析、不渲染、不执行脚本,只当纯模板缓存——这才是真正“零开销”的结构预制方式。
- 必须用
document.createElement('template')或 HTML 中的<template id="item"><div class="item"></div></template>,不能用script type="text/template"—— 后者是字符串,仍需DOMParser解析,开销翻倍 - 插入前务必调用
content.cloneNode(true),否则重复使用会移动节点(不是复制),导致第二次插入时为空 - 若模板含
<slot>或需动态绑定,优先在框架内处理;纯静态结构才用原生template
template + DocumentFragment 组合才是高频更新的安全模式
列表滚动、搜索结果刷新这类场景,单靠 template 还不够:直接 appendChild 每个克隆节点仍会触发多次重排。必须把所有节点先塞进 DocumentFragment,再一次性挂载。
- 错误写法:
frag.appendChild(template.content.cloneNode(true))——template.content是DocumentFragment,不能直接 appendChild 到另一个 fragment,会报错 - 正确写法:
frag.append(...template.content.cloneNode(true).children)或用importNode(兼容旧环境) - 注意:
template内的<style>和<script>在克隆后不会生效,需单独提取或外置
深层嵌套模板要拆分,别让单个 template 超过 6 层深度
模板本身不渲染,但它的结构深度会影响克隆和插入性能。实测显示,单个 template 内部嵌套超 8 层时,cloneNode(true) 耗时增长明显,尤其在低端安卓 WebView 中。
- 卡片类组件别写成:
<template><div><div><div><div><article>...</article></div></div></div></div></template>—— 这等于把 DOM 深度问题前置到模板定义阶段 - 改用组合式模板:
<template id="card-header"><header>...</header></template>+<template id="card-body"><main>...</main></template>,JS 中按需组装 - 服务端返回的 HTML 片段若含深层嵌套,前端不应直接
insertAdjacentHTML,而应先用DOMParser解析,再用template做一次扁平化中转
警惕 SSR 注入的隐性深度:模板里没写,但服务端生成了多层 wrapper
Vue/React SSR 或 PHP 模板引擎常自动包裹一层 <div data-server-rendered="true">,看起来只是 1 层,实际可能带 3–4 层无意义嵌套。这些节点参与样式计算、继承链回溯,且无法被 DevTools 的 “Show DOM properties” 直接标出 depth。
立即学习“前端免费学习笔记(深入)”;
- 验证方法:在 Elements 面板右键节点 →
Show DOM properties,看depth值;若发现某<div>depth 为 7,但源码里只写了 2 层,大概率是 SSR 注入 - 解决不是删模板,而是配置 SSR 输出:Vue 用
ssr: { template: '' }移除默认 wrapper;PHP Twig 加{% verbatim %}绕过自动包装 - 最常被忽略的是:
data-属性本身不增加深度,但配套的 CSS 类(如.ssr-wrapper)若用了display: contents,反而会让子元素继承父级布局上下文,变相延长计算链



















