template.content.cloneNode(true)是获取可操作DOM节点的唯一入口,必须深克隆才能保留表单状态、支持修改与插入,且需避免innerHTML引发XSS及事件丢失。

template.content.cloneNode(true) 是唯一能拿到可操作节点的入口
很多人一上来就用 document.querySelector('template').innerHTML,结果返回空字符串——这是最常踩的坑。因为 template 的内容被封装在 content 属性里,是个只读的 DocumentFragment,innerHTML 对它完全无效。
必须走这一步:template.content.cloneNode(true)。不加 true 是浅克隆,表单值、input 光标、选中状态全丢;加了才真正复制出一份独立、可修改、可插入的 DOM 树副本。
-
template.textContent会返回所有文本(含换行缩进),但无法定位或修改子元素 -
template.innerHTML永远是空字符串,别试 - 克隆后原
template本身仍保持“离线”状态,不影响后续再次克隆
填值时别碰 innerHTML,优先用 textContent 和 value
用 innerHTML 往克隆后的节点里塞用户数据,等于主动打开 XSS 大门。而且它会销毁已存在的事件监听器(比如模板里有个 <button onclick="doSomething()">,克隆后 onclick 就没了)。
- 纯文本内容:用
el.querySelector('.title').textContent = data.title - 表单控件:用
el.querySelector('input').value = data.inputValue,不是textContent - 需要渲染带标签的富文本?先对用户输入做转义(如
text.replace(/&/g, '&').replace(/, '<')),再塞进innerHTML -
<script>和<style>在克隆后依然不执行、不生效,别指望它们“活”过来
批量渲染必须用 DocumentFragment 中转
循环里每轮都 container.appendChild(cloned),浏览器就得反复重排重绘。100 条可能卡顿,500 条基本卡死。这不是理论,是真实性能断崖。
立即学习“前端免费学习笔记(深入)”;
- 先创建中转容器:
const frag = document.createDocumentFragment() - 循环体内:克隆 → 填值 →
frag.appendChild(cloned) - 循环结束后,只调一次
container.appendChild(frag) -
frag是轻量级中间对象,不在 DOM 树中,querySelector查不到它,放心用
template 内部的事件和 slot 都不会自动激活
template 里的 onclick、oninput 等内联事件不会执行,克隆后也不带监听器——因为克隆的是结构,不是行为。slot 同理:slot="header" 这种写法在普通 template 中毫无作用,它只在挂载到 Shadow DOM 时才被解析。
- 正确做法:克隆后显式绑定事件,例如
cloned.querySelector('button').addEventListener('click', handler) - 更推荐事件委托:
container.addEventListener('click', e => { if (e.target.matches('.delete-btn')) { /* 处理 */ } }) - 想传元信息?用
data-属性:cloned.querySelector('li').dataset.id = item.id - 别在
template里写内联 JS,结构和行为必须分离
真正容易被忽略的是:<template> 里的 <style> 和 <script> 标签虽不执行,但依然会被浏览器解析并计入文档资源计数——上线前得手动清理无用代码,否则影响加载指标。



















