template标签仅保存结构,不执行脚本或触发slot分发;slot仅在Shadow DOM直系子节点中生效,且需Custom Elements配合实现指令化响应。

template 标签本身不执行任何指令,只存结构
很多人写 <template><slot name="btn"></slot><script>console.log('run')</script></template>,期待它在插入时自动运行脚本或响应 slot —— 实际上,template 内容被克隆后,<script> 不会执行,<slot> 也不会触发分发。浏览器把它当纯 DOM 片段处理,仅保留结构树。
常见错误现象:
- 把
template直接放进<body>,页面空白,控制台无报错,内容静默丢弃 - 在
template里写<my-button></my-button>,但没注册自定义元素,结果渲染成普通未知标签 - 用
innerHTML = template.innerHTML替代cloneNode(true),导致内部<script>被剥离、事件监听器丢失
正确做法是:把 template 当作“结构草稿”,只用于 document.getElementById(...).content.cloneNode(true) 获取纯净副本,再手动挂载到目标容器(通常是 shadowRoot)。
slot 分发只发生在 Shadow DOM 的直系子节点层级
slot 不是通用占位符,它的行为严格绑定于 Shadow DOM 上下文。没有 attachShadow,<slot> 就是普通标签;即使有 shadowRoot,也只对自定义元素的**直接子元素**做匹配分发。
立即学习“前端免费学习笔记(深入)”;
使用场景中容易踩的坑:
-
<my-card><div><h2 slot="header">Title</h2></div></my-card>→<h2>不会被分发,因为不是<my-card>的直系子节点 - slot name 区分大小写:
slot="Header"和<slot name="header">不匹配 - slot 默认内容(fallback)只在没有匹配子节点时生效,且不会覆盖已插入的内容
验证是否生效最简单的方法:打开 DevTools,展开自定义元素,看 shadowRoot 下是否有 slot 标签,以及其 assignedNodes() 是否返回预期节点。
Custom Elements 是 template + slot 指令化扩展的必要桥梁
想让 template 和 slot 协同工作并支持属性响应、生命周期控制,必须走 Custom Elements 路线。跳过这步,就只是静态 HTML 复制,谈不上“指令化”。
关键步骤不能省略:
- 类必须继承
HTMLElement,构造函数第一行调用super() - 必须在
constructor或connectedCallback中调用this.attachShadow({ mode: 'open' }) - 模板注入要使用
shadowRoot.appendChild(template.content.cloneNode(true)),而非innerHTML - 若需响应属性变化,必须定义
static get observedAttributes()并实现attributeChangedCallback
例如,一个带主题切换的卡片组件,data-theme="dark" 属性变更时,不能靠 CSS class 切换完事——得靠 attributeChangedCallback 主动更新 shadowRoot 内样式或结构,这才是真正的指令化响应。
HTML 模板指令化 ≠ 框架式双向绑定
原生 template + slot + Custom Elements 提供的是单向结构分发与有限响应能力,不支持类似 Vue 的 v-model 或 React 的 JSX 表达式插值。所有动态逻辑都得手写 JS 控制。
典型限制:
- 无法在
template中写{{ title }}或{% if visible %}—— 这些属于模板引擎范畴,原生 HTML 不解析 -
slot不提供作用域,无法把父级数据传入插槽内容;想传参得靠自定义属性 + JS 显式桥接 - 没有内置 diff 算法,DOM 更新全靠手动操作,频繁重渲染易引发性能问题
真正容易被忽略的点是:你以为封装了一个 <user-card>,就能像框架组件一样传 props 渲染;实际上,你得自己监听 name 属性变化、手动更新 shadowRoot 里的 <h3> 文本——这个过程没有任何语法糖,全是 DOM API 调用。



















