template.content.cloneNode(true)是唯一安全克隆方式,因template.content为只读DocumentFragment,只能移动一次,深克隆才能保留事件绑定、文本节点及子节点结构,且须在connectedCallback中执行以确保DOM就绪。

template.content.cloneNode(true) 是唯一安全克隆方式
直接 appendChild(template.content) 看似省事,但会导致第二次实例化时内容为空——template.content 是一个 DocumentFragment,只能被移动一次。一旦移出,再取就是空的。
必须用 cloneNode(true)(true 表示深克隆),否则事件监听器、表单状态、文本节点都会丢失;false 只克隆子元素,不包含文本和绑定上下文。
- 别在
constructor里操作template——此时 DOM 还没解析完,document.getElementById拿不到它 - 模板必须放在 light DOM 中,且用
id定位,不能靠 class 或 data-*,避免多实例冲突 - 推荐把
<template id="xxx">放在</body>前,确保自定义元素定义前已存在
slot 分发只在克隆后挂入 Shadow DOM 才生效
<slot> 不是占位符,也不是字符串替换标记。写在 innerHTML = "<div><slot></slot></div>" 里完全无效——它只会被当普通标签渲染,插槽机制根本不会启动。
只有克隆后的节点被 append() 到启用了 Shadow DOM 的元素内部,<slot> 才开始分发外部传入的子节点。
立即学习“前端免费学习笔记(深入)”;
- 默认插槽用
<slot></slot>;命名插槽必须加name属性,且值严格匹配(大小写敏感、无空格) - 外部使用时:
<my-card><h2 slot="header">标题</h2></my-card>,slot属性值必须与模板内<slot name="header"></slot>一致 - slot fallback 内容(如
<slot>默认文案</slot>)只在外部未传对应内容时显示
动态属性绑定不能靠字符串拼接
想根据 this.getAttribute('size') 切换 class?硬拼 innerHTML = `<div class="${sizeClass}">` 极易漏转义、引号错乱、XSS 风险高,且 IDE 无法语法高亮或格式化。
正确做法是在克隆后立即操作 DOM 节点:用 element.classList.add()、element.textContent = ...、element.setAttribute() 等原生 API。
- 所有动态值都应在克隆完成后再设置,不要留到异步回调里——节点可能已被移除或上下文丢失
- 避免在模板里写
data-*然后 JS 里反复查,直接用getAttribute()+setAttribute()控制更可控 - 若需响应属性变更,必须声明
static get observedAttributes()并实现attributeChangedCallback
构建时注入 template 容易踩的时机坑
Vite、Webpack 等工具通过 HTML 插件注入 <template> 时,常见错误是模板 DOM 节点晚于自定义元素定义执行——JS 先跑 customElements.define(),再注入 template,导致 document.getElementById 查不到。
解决办法不是加 setTimeout,而是确保注入逻辑在 <script> 标签之前,或显式等待 DOMContentLoaded 后再定义组件。
- 本地开发务必起 HTTP 服务(
vite preview或npx http-server),file://协议下模块加载会失败 - 模板内容若含
<script>或<img src>,template本身不执行也不加载——这是设计特性,不是 bug - 调试时在 DevTools 的 Elements 面板看不到
<template>内容,因为它不渲染;要查克隆结果,得看 shadowRoot 或目标容器节点



















