<p>template.content.cloneNode(true) 是唯一安全克隆入口;需通过 content 属性访问 DocumentFragment,浅克隆丢失状态,状态应外显于 data- 属性,填值须按 contentEditable/value/textContent 区分使用。</p>

template.content.cloneNode(true) 是唯一安全克隆入口
很多人卡在第一步:用 document.querySelector('template').innerHTML 拿不到内容,返回空字符串。这是因为 <template> 的真实子节点被包裹在 DocumentFragment 里,只能通过 content 属性访问。template.innerHTML 永远为空,template.textContent 只返回纯文本(含换行缩进),无法操作 DOM 结构。
不加 true 是浅克隆:子元素事件监听器、<input> 光标位置、表单选中态全丢——比如 <input type="checkbox" checked> 克隆后会丢失勾选状态。
状态驱动必须外显化到 data- 属性
别把状态藏在 JS 变量里,否则 CSS 无法响应、测试难断言、调试靠 console.log。正确做法是让组件根节点携带状态标识,例如:<div data-component-state="loading"> 或 <main data-fsm-state="validating">。
JS 更新只需一行:el.dataset.componentState = 'success'(注意 kebab-case 自动转 camelCase);CSS 写成 [data-component-state="success"] .action-btn { display: none; } 即可生效。
常见错误:用 class 控制状态(如 class="state-success"),导致 JS 需手动增删 class 列表,易残留、冲突、漏更新。
填值必须区分 contentEditable/value/textContent
用 innerHTML 拼接用户输入等于主动引入 XSS 风险,还会销毁已绑定的事件监听器(比如模板里原本有 <button onclick="handle()">,克隆后 onclick 就没了)。
正确填值方式如下:
纯文本内容:用 el.querySelector('.title').textContent = data.title
表单控件:用 el.querySelector('input').value = data.value,不是 textContent
需保留 HTML 结构:先对用户输入做转义(如 text.replace(/&/g, '&').replace(/, '<')),再塞进 innerHTML
带 slot 的模板:原生 <template> 不激活 <slot> 机制——它没挂载到 Shadow DOM,slot="xxx" 根本不解析,也无报错,别混用。
立即学习“前端免费学习笔记(深入)”;
批量渲染必须走 DocumentFragment
循环里每轮都 container.appendChild(cloned),等于让浏览器反复重排重绘。100 条数据可能就明显卡顿;500 条基本卡死。
正确做法是:
用 const frag = document.createDocumentFragment() 创建中间容器
循环中调用 frag.appendChild(cloned)
循环结束后,只调一次 container.appendChild(frag)frag 是轻量中间容器,不能被查到,也不在 DOM 树中——这是性能分水岭,不是可选项。
真正容易被忽略的是:template 本身不执行脚本、不加载图片、不触发任何事件。你克隆它、填值、插入 DOM 后,如果模板里有 <script> 或 <img src="...">,这些资源不会自动加载或执行。需要手动触发,且必须确保上下文正确——比如 script 的 this 指向、img 的 base URL、事件监听器重新绑定。这不是 bug,是设计使然。



















