template标签本身不渲染、也不执行脚本,因其内容被浏览器转为离线DocumentFragment挂载在content属性上,不解析DOM、不加载资源、不执行script/style;必须用template.content.cloneNode(true)或document.importNode(tmpl.content, true)克隆后插入DOM才能生效。

为什么template标签本身不渲染、也不执行脚本
浏览器遇到<template>会直接跳过其内容,既不解析 DOM 节点,也不触发图片加载、<script>执行或<style>计算。它只是个离线仓库——你看到的 HTML 结构在 DOM 中完全不存在,document.getElementById()查不到,CSS 选不到,事件也绑不上。
常见误操作是把<template>当 Vue 的<template>用,或者以为写上{{title}}就能自动替换,结果页面一片空白,控制台还不出错。这不是 bug,是规范行为。
-
template.innerHTML返回空字符串,必须用template.content取 DocumentFragment -
cloneNode(true)会丢失表单状态(如<input checked>的勾选态),要用document.importNode(tmpl.content, true) - 模板内
<script>和<style>不会自动生效,得手动注入到shadowRoot或document.head
怎么安全克隆并填充template内容
核心不是“复制”,而是“导入+映射”。document.importNode()能保留表单控件状态、正确处理<select>选项、兼容 Shadow DOM;而填值不能靠字符串替换,得走 DOM API。
- 获取模板:
const tmpl = document.getElementById('card-template') - 克隆内容:
const fragment = document.importNode(tmpl.content, true) - 填充文本:
fragment.querySelector('[data-bind="title"]').textContent = data.title(防 XSS) - 填充 HTML:
fragment.querySelector('[data-bind="content"]').innerHTML = sanitizedHtml(必须先转义或白名单校验) - 挂载:
container.appendChild(fragment),别用innerHTML = ...覆盖已有结构,否则丢焦点、毁事件绑定
如何避免 ID 冲突和样式泄漏
每次克隆出来的节点默认带着原始id,多个实例就会重复——label[for]失效、getElementById()返回第一个、CSS 选择器意外命中多个元素。样式若写在 light DOM,也会全局污染。
立即学习“前端免费学习笔记(深入)”;
- 克隆后重写 ID:
fragment.querySelectorAll('[id]').forEach(el => el.id = el.id + '-'+Math.random().toString(36).substr(2, 9)) - 更可控做法:模板里用占位符如
id="item-{id}",克隆后用fragment.innerHTML = fragment.innerHTML.replace(/{id}/g, uniqueId) - 样式必须进 Shadow DOM,或用
adoptedStyleSheets注入;禁止在<template>里直接写<style>并插入 light DOM - 若需复用外部 CSS,用
:host > ::slotted(*)限定作用域边界
template + 自定义元素时,配置更新怎么触发重渲染
自定义元素没有响应式数据绑定机制。attributeChangedCallback只能监听字符串属性变更,JSON 对象无法直接表达。所谓“更新”,本质是调用一个手动实现的render()函数。
- 把配置存在实例属性上:
this.config = { title: '首页', enabled: true } - 提供公开方法:
updateConfig(newConfig) { this.config = newConfig; this.render(); } -
render()函数要清空容器、克隆template.content、填值、绑事件、再appendChild——不能只改textContent就完事,否则事件丢失 - 禁止在
connectedCallback里异步拉配置,否则首次渲染为空;建议配置就绪后再调用render() - 容易忽略的一点:
template.content是只读的,不能直接往里面塞data-属性做绑定,所有动态逻辑必须由 JS 驱动
template不是“开箱即用”的组件系统,它是个裸金属底座——你要自己搭电源、装轮子、调悬挂,才能跑起来。漏掉importNode、硬写 ID、跳过 XSS 校验、或在 Shadow DOM 里用事件委托,都会让整个架构在某次上线后突然卡住、失焦、或被注入。



















