attributeChangedCallback不会自动触发,必须显式声明static get observedAttributes()返回要监听的属性名数组;属性值恒为字符串,需手动类型转换;data-*属性仅适合初始化传参,非响应式;attribute与JS属性无自动同步,须手动桥接。

attributeChangedCallback 不会自动触发,必须显式声明 observedAttributes
浏览器只在你定义了 static get observedAttributes() 并返回具体属性名数组时,才会对那些属性的变更调用 attributeChangedCallback。写成 return ['data-count'] 就只监听 data-count;写成 return ['count'] 就只监听 count 属性——和 HTML 中写的属性名严格一致,不自动识别 data- 前缀,也不支持通配符。
常见错误:
- 在自定义元素里写了
<my-counter data-count="5">,但observedAttributes返回['count']→ 回调完全不执行 - 用
el.dataset.count = '10'修改值 → DOM attribute 没变,回调不触发 - 在
constructor里就调用this.setAttribute('count', '0')→ 此时元素尚未 upgrade,部分浏览器会静默失败
属性值永远是字符串,类型转换必须手动做
attributeChangedCallback(attrName, oldValue, newValue) 的 newValue 永远是字符串,哪怕你在 HTML 里写的是 count="42" 或 disabled(布尔属性)。不做转换直接用于计算或判断,大概率出错。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 数值型:用
Number(newValue) || 0,避免parseInt('', 10)返回NaN - 布尔型:用
this.hasAttribute('disabled')判断存在性,而不是newValue === 'true' - JSON 类型:必须包
try/catch,JSON.parse(newValue) || {},否则非法 JSON 会让整个回调崩掉 - 空字符串、
null、undefined都可能传进来,别假设它“一定有值”
data-* 属性不能当响应式状态源,只适合初始化传参
dataset 是一次性读取通道,不是响应式管道。你在 HTML 里写 <my-list data-items='[{"id":1}]'>,组件挂载后解析一次就完了;后续改 el.dataset.items = '[{"id":2}]' 不会触发任何回调,也不会更新视图。
真正要响应变化,只有两条路:
- 把
data-items当初始配置,在connectedCallback里解析并存进内部状态对象,之后所有更新走 JS 状态驱动 DOM - 如果非要监听
data-items变更,必须把它列进observedAttributes(即return ['data-items']),且只能通过el.setAttribute('data-items', '...')修改才生效
注意:data-items 和 items 是两个不同属性名,别混淆。
不要依赖 MutationObserver 监听自定义元素的 attribute 变更
给自定义元素套 MutationObserver 监听 attributes: true,看似能捕获所有属性变化,但实际问题很多:
- 无法区分哪些属性是你关心的,得自己遍历过滤,代码冗余
- 监听不到初始化时的 HTML 属性(因为 observer 要等元素挂载后才能 start)
- 回调里拿不到
oldValue/newValue,得自己 diff,容易漏判 - 没生命周期语义,无法和
connectedCallback或disconnectedCallback对齐
除非你在封装一个通用工具类(比如跨框架的属性桥接器),否则直接用 observedAttributes + attributeChangedCallback 更轻量、更标准、更可控。
最易被忽略的一点:attribute 变更和 JS 属性赋值是两套系统。你在组件里写 this.count = 5,不会触发 attributeChangedCallback;反过来,el.setAttribute('count', '5') 也不会自动同步到 this.count。这两者之间没有自动映射,必须手动桥接。



















