标签必须置于 或 内(不能在 中),且须为直接子元素;写在 、、 等非法容器中会被浏览器移除或忽略,导致模板失效。

HTML <template></template> 标签必须用在哪儿?
它只能出现在 、 或 <frameset></frameset> 内部,不能直接写在自定义元素类的 innerHTML 字符串里——否则浏览器会忽略它,或触发解析错误。
常见错误现象:把 <template></template> 当普通 HTML 片段拼进 shadowRoot.innerHTML,结果模板内容被立即渲染,失去“惰性”;或者根本没被克隆,content.cloneNode(true) 返回空。
- 正确做法是先在文档中声明一个
<template id="my-counter"></template>,再在自定义元素中用document.getElementById('my-counter').content.cloneNode(true)获取结构 - 若组件需动态加载模板(如从模块导入),建议用
fetch()加载字符串后调用DOMParser().parseFromString(html, 'text/html')提取<template></template>节点,避免直接 innerHTML 插入 - 注意:
<template></template>中的脚本不会自动执行,图片不会加载,样式不会生效——这是设计特性,不是 bug
innerHTML vs insertAdjacentHTML 绑定属性时怎么选?
两者都能插入含变量的 HTML 字符串,但语义和安全性差异明显。如果你只是把用户输入拼进模板,innerHTML 会执行内联脚本、加载远程资源,极易引入 XSS。
使用场景:仅当数据完全可信(如配置项、静态文案)且需快速渲染时,才考虑 innerHTML;绝大多数属性绑定应走 DOM 操作或 textContent + 属性赋值。
- 推荐优先用
el.textContent = value处理文本内容,el.setAttribute('data-id', id)设置属性,el.className = cls控制样式 - 如果非得用字符串插值(比如服务端返回的富文本片段),用
insertAdjacentHTML('beforeend', htmlStr)更安全:它不重写整个父节点,不影响已绑定事件,也不会清空子节点的 state - 切忌对不可信数据调用
innerHTML,哪怕加了escape也不可靠——浏览器解析器比你想象的更复杂
自定义元素里怎么让属性变更触发 UI 更新?
靠 static get observedAttributes() + attributeChangedCallback,而不是监听 onchange 或轮询。这是 Web Components 的标准响应机制,但容易漏掉几个关键点。
- 必须在
class MyElement extends HTMLElement中显式声明要观察的属性名数组,比如return ['count', 'disabled'] -
attributeChangedCallback(attr, oldVal, newVal)中,oldVal和newVal都是字符串类型,数值属性如count="5"不会自动转成 number,需手动parseInt(newVal) - 首次初始化时,
connectedCallback不会触发该回调;若希望初始值也生效,得在constructor或connectedCallback中主动读取并同步 UI - 注意:布尔属性(如
disabled)在 DOM 中存在即为 true,移除即为 false,newVal在存在时是空字符串"",不是"true"
为什么 shadowRoot 里用 ${value} 插值总失败?
因为 shadowRoot.innerHTML = `...${value}...` 是纯字符串替换,不经过模板引擎解析,也不会重新绑定事件或响应式更新——它只是把当前值“快照”进去,后续 value 变了,DOM 不会变。
这不是语法错误,而是对“模板”的误解:原生 Web Components 没有内置插值语法,${} 只在 JS 字符串模板中有效,一旦赋给 innerHTML 就固化了。
- 真正需要响应式绑定时,要么用
render函数(如 lit-html)每次全量重绘,要么手动在attributeChangedCallback或自定义 setter 中更新对应节点的textContent或value - 如果只是初始化渲染,用
template.content.cloneNode(true)+querySelector找到占位节点再填值,比拼字符串更可控 - 别指望
shadowRoot.querySelector('.counter').textContent = this.count能自动触发重排——它确实能改显示,但没封装逻辑,容易遗漏边界情况(比如 count 为负数时要不要禁用按钮)
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











