template.content必须cloneNode(true)才能重复使用,因其返回DocumentFragment仅可插入一次,二次插入会报错;克隆后需手动处理script执行、ID去重、事件绑定及表单状态同步。

template.content 必须 cloneNode(true) 才能重复使用
直接 appendChild(template.content) 第一次能显示,第二次就失效,控制台报错 Failed to execute 'appendChild' on 'Node': The node to be inserted is a document fragment that is already in the document。这是因为 template.content 返回的是一个 DocumentFragment,它被插入后就从原 fragment 中移出,变成“已消耗”状态。
正确做法永远是复制:
-
const instance = template.content.cloneNode(true)——true是必须的,否则只克隆空节点,不带子元素 - 含
<input>、<select>的模板,不cloneNode(true)就丢失初始值和 DOM 引用 - 每次生成新实例都得重新克隆,不能缓存
template.content直接复用
template 里的 script 和 style 不会自动执行
<template> 内部的 <script> 标签会被解析为节点,但完全不执行;<style> 也不会生效;<img src="..."> 的资源不会加载。这是设计使然,不是 bug。
如果模板里有交互逻辑,必须手动处理:
立即学习“前端免费学习笔记(深入)”;
- 克隆后,遍历
instance.querySelectorAll('script'),用eval()或创建新<script>标签注入 - 内联样式可提前写进
style属性,或通过classList控制 - 自定义元素(如
<my-card>)需确保customElements.define()已执行,否则克隆后仍是未升级的普通标签
innerHTML + insertAdjacentHTML 更适合纯静态替换
如果你只是做变量插值(比如把 {{name}} 替换成 “Alice”),不用走 content.cloneNode(true) 这条路。直接读 template.innerHTML,字符串替换后再用 insertAdjacentHTML() 插入更轻量、无生命周期陷阱。
但要注意边界:
- 避免 XSS:用户输入必须经
textContent或DOMPurify.sanitize()处理,别直接拼进innerHTML - 丢失语义:这种方式绕过了
<template>的惰性机制,图片会立即加载,<script>可能被执行(取决于浏览器策略) - 不含动态绑定:表单控件状态、事件监听器需额外手动绑定,
<slot>也不生效
模板拆分要按语义和复用意图判断
不是所有 HTML 都该塞进 <template>。抽离前先问三个问题:是否多页重复?是否承载独立语义?是否需要 JS 动态控制?
典型该抽的场景:
- 导航栏、弹窗骨架、列表项卡片(如
<template id="user-item">) - 含默认行为的最小完整单元(比如带校验逻辑的
<input>组合)
不该抽的反例:
- 纯样式容器(
<div class="card">)——CSS 足够管住 - 混着硬编码路径或内联 JS 的片段——先清理再抽象
- 宽高写死的
<img>——改用sizes和loading="lazy"
真正麻烦的从来不是怎么激活模板,而是模板本身是否干净、语义清晰、职责单一。没做好这一步,后续所有 clone、绑定、替换都会越来越重。


















