浏览器禁止重定义 button 等原生标签,唯一合法扩展方式是用 { extends: "button" } 配合 is 属性;独立自定义元素无法继承原生 API,必须手动桥接事件、属性及表单能力。
执行 customElements.define("button", ButtonClass) 会立刻抛出错误:Failed to execute 'define' on 'CustomElementRegistry': "button" is not a valid custom element name。这不是兼容性问题,而是浏览器在解析阶段就拦截——所有已知 HTML 标签名(button、input、canvas、table 等约 20 个)都被硬编码拒绝注册。试图绕过(比如改原型链或动态替换)也无效:生成的节点仍是 HTMLButtonElement 实例,你的类根本不会被实例化。
想让 <button> 具备加载状态管理能力,唯一可行路径是组合使用 is 属性和 { extends: "button" }。漏掉任意一环,属性、方法、事件都会失效。
class LoadingButton extends HTMLButtonElement —— 必须继承对应内置类,不能只继承 HTMLElement
super(),否则报 Failed to construct 'HTMLButtonElement',且 this.value、this.disabled 等全部为 undefined
customElements.define("loading-button", LoadingButton, { extends: "button" }) —— { extends: "button" } 不可省略<button is="loading-button">Submit</button>,写成 <loading-button> 或 <button data-is="loading-button"> 全部无效is 值("loading-button")必须与 define() 第一个参数完全一致,大小写、连字符都不能错写 <my-button> 看起来更自由,但它本质是 HTMLElement 实例,没有 click()、focus()、form 关联、value 属性等任何原生按钮能力。你得手动实现:
attachShadow({ mode: "open" }) 封装内部 <button>,再透传事件和属性<my-button value="ok"> 不会自动设置内部 <button> 的 value,必须在 attributeChangedCallback 里手动同步:this.querySelector("button").value = this.getAttribute("value")
form.elements 捕获),必须启用 formAssociated: true 并调用 this.attachInternals(),否则 reportValidity() 和提交值都不可用is 属性的原生标签(如 <div>、<span>)无法用此方式扩展,只能走独立封装路线Chrome 67+、Firefox 63+、Safari 10.1+ 原生支持 v1 规范,但 IE 和旧版 Edge 完全不支持。不引入 polyfill,自定义元素在这些环境里就是普通未知标签,connectedCallback 不触发,customElements.define() 甚至可能静默失败。
立即学习“前端免费学习笔记(深入)”;
@webcomponents/custom-elements,CDN:https://unpkg.com/@webcomponents/[email protected]/custom-elements.min.js
customElements.define() 调用之前加载,放在 <head> 末尾或 <body> 开头最稳妥customElements.upgrade()
getContext()、value、checkValidity() 这些原生 API 的,只有 is 扩展路径;而独立标签看似灵活,实则每项原生能力都要自己桥接,稍有遗漏就会导致表单失联、焦点异常或验证失效。最容易被忽略的是:浏览器对配置错误几乎不报错,只会安静地当它是个普通元素处理——你得靠 el instanceof HTMLButtonElement 或 el.value !== undefined 主动验证是否真的生效。