Web Components 是浏览器原生标准 API,由 Custom Elements、Shadow DOM 和 HTML Templates 组成;需继承 HTMLElement、用 customElements.define() 注册含短横线的标签名,并通过 observedAttributes 和 attributeChangedCallback 响应属性变化。

Web Components 是什么?它不是框架,而是浏览器原生能力
Web Components 不是某个库或工具链,它是浏览器内置的一套标准 API 集合,由 必须继承 因为 自定义元素不会自动监听属性变化。要让 立即学习“Java免费学习笔记(深入)”; 注意: 真正难的不是写出来,而是想清楚哪些状态该暴露为属性、哪些该用 slot 分发、哪些该走事件通信——这些设计决策一旦定下,后续组件组合就很难推翻。Custom Elements、Shadow DOM 和 HTML Templates 三部分组成。其中最直接支撑“可复用自定义元素”的,就是 CustomElementRegistry.define() —— 它让你注册一个带行为的 HTML 标签,比如 ,之后就能像 如何用
class 定义一个可复用的自定义元素?HTMLElement,并在构造函数中初始化 DOM(注意:不能在这里调用 this.innerHTML 或访问 this.children,因为此时元素尚未挂载);生命周期钩子如 connectedCallback 才是插入 Shadow DOM 和绑定事件的安全时机。class MyButton extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'closed' });
const template = document.createElement('template');
template.innerHTML = `
`;
shadow.appendChild(template.content.cloneNode(true));
}
connectedCallback() {
this.shadowRoot.querySelector('button').addEventListener('click', () => {
this.dispatchEvent(new CustomEvent('my-click', { bubbles: true }));
});
}
}
customElements.define('my-button', MyButton);
attachShadow({ mode: 'closed' }) 阻止外部 JS 访问 Shadow DOM,提升封装性
是内容分发点,允许用户写 customElements.define() 注册,且标签名中必须含短横线(-),否则会报 Failed to execute 'define' on 'CustomElementRegistry': The name provided must contain a dash
为什么不用
innerHTML 直接写 Shadow DOM?innerHTML 会忽略 标签的 scoped 行为,也无法自动解析 。更关键的是:直接赋值字符串无法触发浏览器对内联样式和模板的优化路径,且易引入 XSS(除非你严格 sanitize)。用 template + cloneNode(true) 是标准做法,它保证样式隔离、内容插槽生效,并被所有现代浏览器正确识别。
template.content.cloneNode(true),多次实例化时会复用同一份节点,导致事件重复绑定或状态污染mode: 'closed' 下,this.shadowRoot 在外部为 null,调试时别误以为没创建成功属性变更如何响应?得靠
static get observedAttributes()
my-button disabled 或 my-button size="large" 触发逻辑更新,必须显式声明观察列表,并在 attributeChangedCallback 中处理。class MyButton extends HTMLElement {
static get observedAttributes() {
return ['disabled', 'size'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'disabled') {
this.shadowRoot.querySelector('button').disabled = newValue !== null;
}
if (name === 'size' && newValue === 'large') {
this.shadowRoot.querySelector('button').style.padding = '12px 24px';
}
}
}
attributeChangedCallback 不会在元素首次初始化时触发(即使 HTML 中已写属性),只响应后续 JS 修改或 setAttribute 调用。初始状态得在 connectedCallback 或构造函数里手动同步一次。











