自定义元素首屏性能关键在定义时机和升级行为轻量:首屏组件需在defer脚本中同步定义,避免全局提前注册;非首屏组件按需动态导入注册;connectedCallback和构造函数须保持轻量,禁止同步重操作。

自定义元素本身不加速首屏,提前 customElements.define() 反而拖慢渲染;关键在「定义时机」和「升级行为轻量」。
首屏组件必须在 defer 脚本中同步定义
浏览器解析到未注册的标签(如 <product-card>)时,会先挂载为 unknown 元素,等定义后才升级——这意味着定义可以晚于 HTML 解析,但不能晚于首次访问(比如 document.querySelector('product-card') 或 DOMContentLoaded 后第一个 microtask)。
- 把首屏用到的组件定义逻辑放进
<script type="module" defer>,且该脚本必须放在</body>前 - 避免在全局
<script>顶部批量注册所有组件,尤其当它们依赖未就绪模块或大量模板时 - SSR 页面中禁止在 HTML 里直接写
customElements.define():hydration 前执行会失败,hydration 后又可能重复定义
非首屏组件必须按需动态导入 + 注册
只延迟 customElements.define() 不够,组件类本身还得从主 bundle 中剥离,否则仍会拉高首屏 JS 体积。
- 用户触发交互(如点击按钮、打开弹窗)时,用
await import('./components/export-modal.js')加载模块 - 确保模块导出默认 class,且无副作用(不自动注册、不注入全局样式)
- 注册后立即调用
customElements.get('export-modal')验证是否成功,避免静默失败
connectedCallback 里不能做重活
这个回调是元素首次被插入 DOM 时触发的升级入口,同步执行耗时操作会直接阻塞渲染树构建和首屏绘制。
立即学习“前端免费学习笔记(深入)”;
- 禁止在
connectedCallback中发起同步网络请求(fetch不带await仍是异步,但常见误写成同步 XHR) - 避免执行复杂计算、遍历长数组、生成大量 DOM 节点
- 有 Shadow DOM 的组件,优先用
adoptedStyleSheets注入样式,而非在模板里写<style>标签——后者每次克隆都会重新解析
构造函数和 attributeChangedCallback 要足够轻
构造函数在元素创建时立刻执行(包括 document.createElement('x-foo') 或 HTML 解析阶段),attributeChangedCallback 在属性变更时同步触发,两者都处于关键路径上。
- 构造函数里只做最小初始化:设置
this.attachShadow()、绑定事件监听器(用箭头函数或bind)、初始化空属性对象 - 不要在构造函数里读取
this.innerHTML或调用getBoundingClientRect()—— 此时元素尚未插入 DOM,尺寸为 0 -
attributeChangedCallback中避免直接修改 DOM 或触发布局,改用requestAnimationFrame延迟到下一帧
最容易被忽略的是:定义完成 ≠ 渲染完成。哪怕 customElements.define() 成功,只要 connectedCallback 里同步 fetch 数据或渲染 200 行表格,首屏照样卡顿——性能瓶颈不在“定义”,而在“升级那一刻做了什么”。



















