attributeChangedCallback仅响应显式声明的attribute变更,需配合observedAttributes且严格匹配大小写和连字符;参数全为字符串,oldValue首次为null;不响应property赋值,初始值需在connectedCallback中读取。

attributeChangedCallback 不会自动响应任意属性变更,必须配合 observedAttributes 显式声明,且只对 attribute 操作生效 —— 直接赋值 this.disabled = true 完全不会触发它。
observedAttributes 必须显式声明且拼写完全一致
浏览器不会猜你要监听什么属性。哪怕你写了 <my-input max-length="10">,也必须在类里写 static get observedAttributes() { return ['max-length']; }。常见错误包括:
-
maxLength或maxlength—— 都不会命中,HTML 属性名是max-length,就必须一模一样 -
data-id和dataId是两个不同属性,后者根本不存在于 attribute 层面 - 首字母大写如
Label,HTML 中写成<my-el Label="x">,JS 里写['Label']也不行 —— attribute 名永远小写 - 数字开头的属性如
3d-enabled可以用,但 HTML 和 JS 中都得写成'3d-enabled'
attributeChangedCallback 的参数陷阱
回调签名固定为 attributeChangedCallback(attrName, oldValue, newValue),三个参数全是字符串,且 oldValue 在首次设置时是 null,不是空字符串或 undefined:
-
<my-input value="42">→newValue是"42",不是数字42;需手动Number(newValue) -
if (!oldValue)会把首次设置误判为“无变化”,应改用oldValue === null -
Number(newValue) + 1在首次调用时会变成NaN,因为Number(null) === NaN - 布尔属性如
disabled,移除时newValue是null,设为""或"disabled"都算“启用”,需按语义判断
它只响应 attribute 操作,不响应 property 赋值
这是最常踩坑的地方:这个回调和 DOM 属性(attribute)绑定,和 JS 对象属性(property)无关。
立即学习“前端免费学习笔记(深入)”;
- ✅ 触发:
el.setAttribute('label', 'new')、el.removeAttribute('disabled')、HTML 初始写入<my-el label="init"> - ❌ 不触发:
el.label = 'new'、el.disabled = true—— 这些是 property 操作,浏览器根本不走这个回调 - 若需同步 property 和 attribute,得自己在 setter 里补
this.setAttribute('label', val) - 从 React/Vue 等框架传值时,它们通常走
setAttribute,所以能触发;但若框架内部直接改 property,就不会
初始属性值不会进 callback,初始化逻辑得放 connectedCallback 里
写 <my-card title="Hello"> 时,title 是初始 attribute,但 attributeChangedCallback 一次都不调 —— 它只响应“变更”,不负责“读取初始值”。
- 必须在
connectedCallback中手动读:const title = this.getAttribute('title') - 别指望靠
attributeChangedCallback做首次渲染,它只是更新通道 - 如果同时需要响应变更 + 处理初始值,两个地方都要写逻辑,或封装一个
updateTitle()方法统一调用 - Shadow DOM 内容填充建议放在
queueMicrotask(() => { /* 此时 this.shadowRoot 已就绪 */ }),避免 timing 问题
真正麻烦的不是写回调,而是区分清楚:哪些操作改的是 attribute(可被监听),哪些改的是 property(完全绕过)。漏掉这个边界,attributeChangedCallback 就像没写一样。



















