自定义元素是浏览器原生支持的可复用组件,具备语义、生命周期和属性绑定能力;不用div因其无语义、无生命周期、易漏清理、样式冲突;需含短横线命名、检查重复注册、继承htmlelement、声明observedattributes。

什么是自定义元素,为什么不用 div 包一层就完事?
因为原生 div 没有语义、没有生命周期、不能复用状态,每次手动 document.createElement('div') + 插入 + 定时移除,容易漏清理、重复触发、样式冲突。自定义元素(Custom Element)让浏览器真正“认识”你的组件:它能响应 connectedCallback、自动升级、支持属性绑定,且天然隔离样式作用域(配合 Shadow DOM)。不写框架也能做到类似 Toast 的声明式调用。
customElements.define() 注册时最容易踩的命名坑
浏览器强制要求自定义标签名必须包含短横线(-),比如 notify-toast 合法,toast 或 NotifyToast 直接报错 Failed to execute 'define' on 'CustomElementRegistry': The name provided must contain a dash。注册前还得检查是否已存在,否则热重载或多次加载脚本会抛 DOMException: Failed to execute 'define' on 'CustomElementRegistry': the name "notify-toast" has already been used。
- 用
if (!customElements.get('notify-toast')) { customElements.define('notify-toast', NotifyToast); } - 类名必须继承
HTMLElement,不能是普通函数 - 属性变更需通过
static get observedAttributes()显式声明,否则attributeChangedCallback不触发
如何让 notify-toast 支持全局调用(比如从任意模块直接 show())?
自定义元素本身是实例级的,但通知场景需要跨组件触发。最轻量做法是暴露一个全局函数,内部创建并挂载实例——不是把实例挂到 window 上,而是每次调用都新建一个临时实例并自动销毁:
function toast(message, options = {}) {
const el = document.createElement('notify-toast');
el.textContent = message;
Object.keys(options).forEach(key => el.setAttribute(key, options[key]));
document.body.appendChild(el);
// 自动移除逻辑写在元素内部,见下一条
}
关键点:这个函数不持有引用,也不依赖已有 DOM 节点,避免内存泄漏;所有配置(如 duration、type)都转为 HTML 属性传入,由元素自己解析。
connectedCallback 和 disconnectedCallback 怎么配合做自动销毁?
很多实现只在 connectedCallback 里启动定时器,却忘了用户手动 remove() 时定时器还在跑——导致 DOM 已删但回调仍执行,甚至操作不存在的节点报错。正确做法是:定时器 ID 存在实例上,disconnectedCallback 清掉它。
-
connectedCallback()中调用this._timer = setTimeout(() => this.remove(), this.duration || 3000) -
disconnectedCallback()中加clearTimeout(this._timer) - 如果支持手动关闭(如点击 ×),也要同步
clearTimeout并调用this.remove() - 别在
remove()后继续读取this.textContent等属性——此时disconnectedCallback已执行,部分属性可能失效
Shadow DOM 里样式隔离了,但事件监听要小心:click 冒泡到 body 可能触发其他逻辑,建议用 event.stopPropagation() 或监听 shadowRoot 内部元素。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











