customelements.define()必须在html解析完成前执行,否则已写入的标签将永久作为htmlunknownelement存在,不触发connectedcallback且无法自动升级;正确做法是用包裹定义逻辑,或在cms场景下手动调用customelements.upgrade()。

customElements.define() 必须在 HTML 解析完成前执行
否则页面里已写好的 <my-card></my-card> 标签永远是 HTMLUnknownElement,不会触发 connectedCallback,也不升级。浏览器解析 HTML 时遇到未定义的自定义标签,只创建空壳节点,之后再 define() 不会自动补升级。
常见错误:把所有 customElements.define() 放在 window.onload、DOMContentLoaded 回调或动态 import() 成功后——这时 DOM 已建好,旧节点卡死不动。
- 正确做法:用
<script type="module" defer></script>包裹定义逻辑,确保脚本在 HTML 解析中途就绪(模块脚本默认延迟执行但不阻塞解析) - 若服务端无法控制 HTML 输出顺序(如 CMS 渲染),必须手动调用
customElements.upgrade(document.body)或遍历匹配节点逐个upgrade() - 注意:重复调用
upgrade()对已升级节点无害,但对大量节点应分片处理(如用queueMicrotask拆解)
非首屏组件别提前 define,更别提前加载 JS
定义自定义元素只是注册构造函数,不等于渲染;但提前 define() 会立即解析类体、初始化静态属性、绑定原型方法——这些开销白费,尤其当组件依赖未就绪或根本没被用到时。
真正按需,得两步走:先延迟 JS 模块加载,再延迟 define()。比如弹窗、设置面板这类交互才出现的组件,完全可等到用户点击再拉代码。
- 推荐写法:
button.addEventListener('click', async () => { const { default: MyDialog } = await import('./my-dialog.js'); customElements.define('my-dialog', MyDialog); }); - 想预加载但不阻塞?用
import('./my-dialog.js').catch(() => {})触发 fetch,不await,等真要用了再define() - 绝对避免:
for (const comp of components) customElements.define(comp.name, comp.cls)这种批量注册——CPU 和内存都浪费在闲置代码上
connectedCallback 里别当 constructor 用
connectedCallback 不是“初始化一次”的钩子,而是“每次插入活跃 DOM”都会触发。列表滚动重用、display: none 切回、框架 diff 后重新挂载,都会反复进来。直接在里面创建 Shadow DOM、绑事件、发请求,必然导致重复执行。
如果你了解HTML,CSS和JavaScript,您已经拥有所需的工具开发Android应用程序。本动手本书展示了如何使用这些开源web标准设计和建造,可适应任何Android设备的应用程序 - 无需使用Java。您将学习如何创建一个在您选择的平台的Android友好的网络应用程序,然后转换与自由PhoneGap框架到一个原生的Android应用程序。了解为什么设备无关的移动应用是未来的潮流,并开始构建应用程序,提供更
典型症状:点开弹窗两次,里面按钮绑了四次 click;滚动列表,每个项的定时器越叠越多;图片组件重复发起 fetch。
- 加守卫字段:
if (this._initialized) return; this._initialized = true; - Shadow DOM 注入必须放
queueMicrotask(() => { this.attachShadow({ mode: 'open' }); }),否则this.shadowRoot可能为null - 异步操作(如
fetch)务必配AbortController,并在disconnectedCallback中abort()
:not(:defined) 是隐藏未升级元素的唯一可靠方式
很多人用 visibility: hidden 或 display: none 想“等定义好了再显示”,但这是错的:未定义的元素压根不匹配 my-button 选择器,CSS 规则根本不会应用;而一旦定义完成,所有同名元素立刻升级并生效,可能造成布局跳动。
:defined 是唯一由浏览器原生保障的伪类,它只在 upgrade 完成后为 true。用它做渐进式样式控制,既安全又精准。
- 推荐写法:
my-button:not(:defined) { visibility: hidden; }—— 元素存在但未定义时不可见,定义后自动可见 - 不要写
my-button { display: none; } my-button:defined { display: block; },因为未定义时规则不匹配,display: none不生效 - 搭配
transition可实现平滑入场:my-button { opacity: 0; transition: opacity 0.2s; } my-button:defined { opacity: 1; }
最容易被忽略的是:自定义元素的性能瓶颈不在 define() 那一刻,而在节点挂载后的初始化行为和 DOM 遍历方式。哪怕注册再快,connectedCallback 里一次全量 querySelectorAll 或反复 innerHTML 赋值,照样让 LCP 崩盘。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










