自定义元素必须用customElements.define()注册且标签名含短横线,类需继承HTMLElement;属性监听需observedAttributes与attributeChangedCallback配合;应使用Shadow DOM封装并用<slot>分发内容;构造函数中不可访问DOM,connectedCallback才可用。

自定义元素必须用 customElements.define() 注册,不能直接 new
浏览器不认你写的 <my-button> 标签,除非它被正式注册。直接写 document.createElement('my-button') 会创建一个未知的 HTMLUnknownElement,样式、事件、生命周期钩子全失效。
正确做法是先定义类,再注册:
class MyButton extends HTMLElement {
connectedCallback() {
this.innerHTML = `<button>${this.textContent}</button>`;
}
}
customElements.define('my-button', MyButton);
- 标签名必须含短横线(
-),如my-button、data-table,mybutton会报错 - 类必须继承
HTMLElement,不能继承HTMLButtonElement等具体类型(部分浏览器不支持) - 注册必须在
DOMContentLoaded前或后执行,但不能晚于元素实际出现在 DOM 中(否则需手动升级:customElements.upgrade(el))
属性变更要用 static get observedAttributes() + attributeChangedCallback</H3>
<p>想让 <code><my-button disabled>
或 <my-button label="保存"> 触发响应?光靠监听 click 不够——属性变化本身不会自动触发逻辑。
必须显式声明观察哪些属性,并实现回调:
立即学习“前端免费学习笔记(深入)”;
class MyButton extends HTMLElement {
static get observedAttributes() {
return ['label', 'disabled'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'label') {
this.querySelector('button').textContent = newValue;
}
if (name === 'disabled') {
this.querySelector('button').disabled = newValue !== null;
}
}
}
- 只监听你在
observedAttributes里列出来的属性;拼写错误或大小写不对(如'Disabled')会导致回调完全不触发 -
oldValue和newValue都是字符串,布尔属性如disabled的值是""(存在)或null(不存在),不是true/false - 首次初始化时,如果属性已在 HTML 中声明,
attributeChangedCallback会立即执行一次
内部 DOM 操作优先用 Shadow DOM,别直接改 innerHTML
自定义元素的核心价值之一是封装与隔离。把模板硬塞进 this.innerHTML 会污染全局样式、泄漏内部结构、且无法阻止外部 CSS 泄入。
正确姿势是挂载 Shadow Root:
class MyButton extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'closed' });
shadow.innerHTML = `<button><slot></slot></button>`;
}
}
-
mode: 'closed'能防止外部 JS 通过el.shadowRoot访问内部节点('open'则可访问) -
<slot>是内容分发入口,允许用户写<my-button>提交</my-button>并透传文本 - 避免在
connectedCallback里重复调用attachShadow——它只能调用一次,重复会抛InvalidStateError
批量操作自定义元素时,注意 connectedCallback 触发时机不可控
如果你一次性插入 100 个 <my-button>,浏览器可能合并触发 connectedCallback,也可能逐个触发,甚至跨帧延迟。别假设它们按顺序、同步执行。
- 不要在
connectedCallback里依赖兄弟元素已就绪(比如想读取相邻<my-input>的值) - 需要协调多个实例时,用
CustomEvent通信,而不是直接 DOM 查询 - 若需确保所有实例初始化完成,可用
requestIdleCallback或Promise.resolve().then()延迟到 microtask 队列末尾
最易被忽略的一点:自定义元素的构造函数里不能访问 this.innerHTML 或 this.children,此时 DOM 还未解析完毕——所有初始内容要等 connectedCallback 才真正可用。



















