connectedcallback 在元素真正插入 document 主树时触发,而非定义时;constructor 仅用于初始化字段和 shadowroot,heavy work 必须移至 connectedcallback;attributechangedcallback 不响应初始属性,需在 connectedcallback 中手动读取。

connectedCallback 触发时机直接决定首次挂载延迟
它不是“一定义就执行”,而是浏览器在完成 HTML 解析、构建 DOM 树、并把该元素真正插入到 document 主树时才调用。这意味着:如果自定义元素出现在首屏 HTML 中,它的 connectedCallback 会卡在 DOM 构建完成后、渲染前的临界点;如果动态创建再 appendChild(),则延迟叠加在 JS 执行之后。
常见误判是以为 customElements.define('x-foo', XFoo) 一执行,组件就“活了”——其实此时只是注册了一个类,DOM 节点仍是普通文本,不触发任何生命周期。
- SSR 输出的
<x-foo data-id="123"></x-foo>,只有等到浏览器解析完这一段并 append 到 body 后,connectedCallback才会进 - 用
innerHTML = '<x-foo></x-foo>'插入,不会触发connectedCallback—— 因为浏览器不走原生插入路径,而是字符串解析后批量挂载,绕过了生命周期钩子 - 框架(如 React)里渲染
<x-foo></x-foo>,是否触发取决于框架是否调用appendChild原生 API;多数现代框架默认不走,需手动ref+appendChild或用 custom element adapter
attributeChangedCallback 不监听初始属性,但影响 hydration 一致性
这个钩子只响应后续的 setAttribute() 或 HTML 属性变更重解析,**不响应首次渲染时的属性值**。也就是说,你在 SSR 模板里写的 <x-input value="hello"></x-input>,attributeChangedCallback 不会拿到 value 的初始值,oldValue 是 null。
这导致一个隐蔽问题:服务端输出的 DOM 和客户端 hydration 后的状态可能不一致。比如组件在 connectedCallback 里读 this.getAttribute('value') 初始化 UI,但若没同步写到内部 property,后续用户输入再触发 attributeChangedCallback,就可能出现 UI 和 attribute 不同步。
- 必须在
connectedCallback中手动读取初始属性:const initVal = this.getAttribute('value'); -
observedAttributes必须显式返回数组,如static get observedAttributes() { return ['value', 'disabled']; };漏写或大小写错(如写成'Value')会导致静默失效 - HTML 中写
data-user-id="42",JS 里必须监听'data-user-id',不能简写为'userId'或'dataset.userid'
constructor 里做 heavy work 会拖慢 DOM 构建阶段
很多人在 constructor 里直接调 fetch、解析 JSON、甚至初始化复杂状态对象——这发生在浏览器还在构建 DOM 树的过程中,会阻塞整个解析流程,尤其对首屏关键路径影响明显。
更糟的是,constructor 中 this.shadowRoot 一定为 null,任何 DOM 操作都会失败;而错误又常被静默吞掉,调试时只能看到组件空白。
-
constructor只做三件事:调super()、this.attachShadow({ mode: 'open' })、声明私有字段(如this._timer = null) - 所有异步操作、DOM 查询、事件绑定、属性读取,必须挪到
connectedCallback里 - 如果组件支持多次挂载(如路由复用),
connectedCallback需加 guard 判断是否已初始化,避免重复执行
disconnectedCallback 不可靠,但清理逻辑放错位置会泄漏内存
它只在元素被显式从 DOM 移除时大概率触发,但页面刷新、标签页关闭、后台进程回收等场景下,浏览器可能直接销毁上下文,跳过此回调。这意味着:依赖它做关键清理(如取消 fetch、清除定时器)是危险的。
更麻烦的是,如果清理逻辑写在 connectedCallback 里却没配对移除,或者用了 addEventListener 但没存引用,就极易造成内存泄漏——尤其在长列表、频繁切换的 SPA 中。
- 定时器必须存为实例字段:
this._timer = setTimeout(...),并在disconnectedCallback里clearTimeout(this._timer) - 事件监听器要带 options:用
el.addEventListener('click', handler, { once: true })或显式保存 listener 引用以便移除 - fetch 请求建议用
AbortController,并在disconnectedCallback中调abort();但需注意,abort 后 promise 仍会 reject,得 catch 住避免 unhandled rejection - 别在
disconnectedCallback里访问document或其他已脱离 DOM 的节点,此时引用可能已失效
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











