是标准、轻量、无副作用的 html 片段缓存容器,内容不渲染、不请求资源、不执行脚本;克隆需用 template.content.clonenode(true),插入真实 dom 后才触发加载与绑定。

用 <template></template> 存储 HTML 片段,不渲染、不请求资源
<template></template> 标签本身不会被浏览器解析或渲染,里面的内容(包括 <img src>、background-image、<script></script>)完全静默,既不发起网络请求,也不执行脚本。这是它和 <script type="text/x-template"></script> 的关键区别——后者是 hack,而 <template></template> 是标准、轻量、无副作用的缓存容器。
常见错误现象:把内容直接写在 <div> 里再用 <code>display: none 隐藏,结果图片照样加载、脚本照样执行、样式照样计算,白占带宽和 CPU。
- 必须用
template.content.cloneNode(true)克隆,不能直接取innerHTML—— 否则会丢失事件监听器、表单状态、自定义元素实例 - 克隆后需手动更新唯一属性(如
id、for、data-id),否则多个实例会冲突 - 模板内不要写
<style></style>标签——它会被克隆但不会生效;样式应提前注入或通过 class 复用
动态插入时才触发资源加载和 DOM 绑定
只有调用 appendChild() 或 insertAdjacentElement() 把克隆节点挂到真实 DOM 上,<img> 才真正发起请求,getBoundingClientRect() 才能读到尺寸,事件监听器才开始响应。
使用场景:弹窗、卡片列表、表单项分组、多步骤表单的每一步 UI。比如点击「添加地址」按钮后,从 <template id="address-form"></template> 中克隆并插入新表单块。
- 插入前可先修改克隆节点的文本、
value、checked等状态,避免后续反复 query - 若需绑定事件,推荐在克隆后立即用
addEventListener,而不是依赖委托——委托对动态插入的同类节点虽可行,但容易漏掉初始化逻辑 - 不要在模板里写内联
onclick或oninput——它们绑定的是原始字符串,克隆后无法自动重连函数引用
与 import() 和 fetch() 的分工边界
<template></template> 只适合已知结构、静态或半静态的 UI 块;它不负责异步加载,也不处理数据映射。需要远程内容或复杂逻辑时,得组合其他机制。
- 纯本地模板 → 直接克隆:
document.getElementById('card-template').content.cloneNode(true) - 需服务端动态生成 HTML 片段 → 用
fetch('/api/card?user=123')+innerHTML,但注意 XSS 和脚本不执行问题 - 需完整组件行为(响应式、状态管理、生命周期)→ 不要用纯 HTML 模板,改用
import()加载 Vue/React 组件,或封装为自定义元素 - 想复用模板但跨页面 → 把
<template></template>提到或单独 HTML 文件中,用fetch()加载后注入document.head再解析
容易被忽略的细节:克隆后样式作用域和焦点管理
克隆节点插入后,CSS 会按正常规则匹配,但若原模板所在位置有局部样式(如 :scope > .item),克隆体可能不命中;焦点也不会自动落到新表单第一个输入框上。
- 避免用
!important强行覆盖,而是确保 CSS 选择器足够明确,或给克隆节点加特定 class 做隔离 - 插入后主动调用
el.querySelector('input, textarea, [contenteditable]').focus(),否则键盘用户无法立即操作 - 如果模板含
<select></select>或<details></details>,其展开/选中状态不会继承,需手动同步初始值 - 用
template.content.firstElementChild而不是template.firstElementChild——后者是<template></template>自身,永远为空











