customElements.define() 是唯一让 HTML 元素响应属性变化的入口,但需手动处理 dataset 类型转换、attributeChangedCallback 监听裸属性名、connectedCallback 中初始化状态,且表单控件需手动桥接 value/checked 等属性。

customElements.define() 是唯一能真正让 HTML 元素“响应属性变化”的入口,但它的绑定不是自动的——你得自己读 dataset、转类型、同步状态、监听变更。框架里的 v-model 或 ngModel 那套是封装好的,原生没有。
data-* 属性只传字符串,不触发任何更新
写 data-count="42" 或 data-disabled="true",JS 里拿到的永远是字符串 "42" 和 "true",不会自动变成数字或布尔值。this.dataset.count 是 "42",if (this.dataset.disabled) 永远为真(非空字符串是 truthy)。
常见错误:直接拿 dataset 值做逻辑判断或数学运算,结果出乎意料。
- 初始化时必须手动转换:
parseInt(this.dataset.count, 10)、this.dataset.disabled === "true" -
dataset中的连字符名自动转驼峰:data-show-avatar→this.dataset.showAvatar,但data-UID不会变,得写成data-uid - 别在
constructor()里读dataset子节点或操作innerHTML—— 此时 DOM 还没挂载,子元素不可靠
attributeChangedCallback 不监听 data-*,只认裸属性名
想让 disabled 属性变化被捕捉,不能写 observedAttributes: ['data-disabled'] —— 这是错的。浏览器把 data-disabled 当作一个完整属性名,但自定义元素 API 要求你声明去掉 data- 后的部分:['disabled']。
-
static get observedAttributes()只接受合法 HTML 属性名(小写、连字符,不能含下划线或大写) - 首次从 HTML 解析元素时,
attributeChangedCallback也会触发,oldValue为null - 回调里必须显式判断:
if (name === 'disabled') { this._disabled = newValue === 'true'; } - 没列在
observedAttributes里的属性,改了也不会触发回调
connectedCallback 是初始化映射最安全的位置
connectedCallback 是 DOM 挂载完成后的第一个钩子,此时 dataset 可读、子节点可用、样式已计算。比 constructor 稳定,比 attributeChangedCallback 更适合一次性初始化。
立即学习“前端免费学习笔记(深入)”;
- 在这里统一读取
this.dataset,做类型转换,赋值给内部状态(如this._count = parseInt(this.dataset.count, 10) || 0) - 如果需要后续响应属性变更,再配合
observedAttributes+attributeChangedCallback更新状态 - 避免在多个生命周期里重复解析同一组
dataset,容易覆盖或漏转
表单控件绑定要单独处理 value/checked/selected
自定义元素里如果包含 <input>、<select>、<textarea>,它们的值不随 data- 属性变化而变,也不自动同步到父元素状态。必须手动桥接。
- 文本类用
.value,复选框/单选框用.checked,<select>用.value或.selectedIndex - 监听
input事件(不是change)保证实时性;change在失焦后才触发 - 不要用
form.reset()重置绑定 —— 它只恢复 HTML 初始值,和你的 JS 数据对象完全脱钩 - 若需双向同步,得在
attributeChangedCallback更新 DOM,在事件监听器里更新数据对象
真正麻烦的不是怎么写,而是哪一步该做什么转换、在哪一时刻读什么值、谁负责清理副作用。比如 dataset.count 是字符串,this._count 是数字,<input>.value 是字符串,三者之间没有自动管道——全靠你手写映射逻辑,漏一处就断链。



















