template 本身不支持配置,所有“可配置”能力必须靠 JavaScript 手动注入数据、绑定事件、校验输入——它只是个结构容器,不是 Vue 或 React 那种响应式模板引擎。

template 本身不支持配置,所有“可配置”能力必须靠 JavaScript 手动注入数据、绑定事件、校验输入——它只是个结构容器,不是 Vue 或 React 那种响应式模板引擎。
为什么不能直接在 template 里写 {{title}} 或 v-if
template.content 是只读的 DocumentFragment,浏览器不会解析其中的占位符或指令。你看到的 {{title}} 就是纯文本,不会自动替换;v-if 在原生 HTML 中根本无效,除非你用 Vue 加载并接管整个节点。常见错误是把 Mustache 或 Vue 写法直接塞进 template,结果页面啥也不显示,控制台也没报错,只有一片空白。
真正起作用的是:你用 JS 拿到克隆后的节点,再手动填值:
const clone = template.content.cloneNode(true);
clone.querySelector('.title').textContent = config.title;
clone.querySelector('img').src = config.avatarUrl || '';
clone.querySelector('button').disabled = !config.enabled;
template 克隆后必须重写 ID 和重新绑定事件
克隆出的节点默认保留原始 id,多个实例会导致 ID 冲突(违反 HTML 规范),影响 label[for]、getElementById 等行为。样式若写在 light DOM,也会全局泄漏。
立即学习“前端免费学习笔记(深入)”;
使用 PptxGenJS 和 Python 解析,将结构化的 HTML/CSS 幻灯片内容转换为原生、可编辑的 PowerPoint(.pptx)演示文稿。
- 禁止在模板内写
<style></style>标签并插入 light DOM;应使用adoptedStyleSheets或shadowRoot.appendChild(style) - 克隆后执行
clone.querySelectorAll('[id]').forEach(el => el.id += '-'+Date.now())(简单场景) - 更健壮做法:模板中用占位符如
id="item-title-{id}",克隆后用正则替换 - 按钮等交互节点,应在克隆后立即绑定事件,不要依赖事件委托(因 Shadow DOM 隔离)
大规模列表滚动时,性能瓶颈不在模板,而在 DOM 总量
即使用了 <template></template> 和自定义元素,一次性渲染 10000 条仍会卡顿 —— 不是模板问题,而是 DOM 节点总量和布局计算压力。
实测千条数据渲染,template.content.cloneNode(true) 比字符串拼接快 2–3 倍(Chrome 128+),但超过 5000 条后帧率明显下降。此时必须引入虚拟滚动或分页机制,而非优化克隆逻辑。
- 禁用
innerHTML拼接列表项:触发完整 DOM 重建、丢失事件绑定、破坏焦点、易 XSS - 预解析结构、克隆
DocumentFragment,渲染更快更安全 - 每次更新都要清空容器再 append,不能靠 diff 或 patch —— 原生无此能力
自定义元素中如何让配置更新触发重渲染
attributeChangedCallback 只监听字符串属性变更,JSON 类型的配置对象无法被 DOM attribute 直接表达。所以实际做法是:
- 把配置对象存在实例属性上,比如
this.config = { title: '首页', ctaText: '立即开始' }; - 提供一个公开方法
updateConfig(newConfig),内部调用this.render() -
render()函数负责清空容器、克隆template.content、填充数据、绑定事件、再append进去 - 不要在
connectedCallback里做异步加载配置,否则首次渲染可能为空;建议配置就绪后再调用render()
容易忽略的一点:template.content 是只读的,不能直接修改;所有动态行为都发生在克隆副本上,且每次渲染都是全新节点 —— 没有 diff,没有缓存,只有明确的“销毁 → 克隆 → 填充 → 插入”流程。


















