ID选择器用于强制唯一标识(如表单关联、锚点跳转),类选择器用于可复用的样式与行为分组;ID高优先级破坏维护性,JS查询容错性差,仅在语义刚性场景才必须使用。
根本区别不在写法(.btn vs #header),而在 HTML 规范对 class 和 id 属性的定义:一个允许多次复用,一个强制全局唯一。
这意味着:class="save-btn" 可以出现在 10 个按钮上,浏览器照常匹配;而 id="save-btn" 出现在两个元素上,document.getElementById('save-btn') 只返回第一个,CSS 样式也只生效于第一个——不是“错”,而是规范如此。
id 是锚点、ARIA 关联、Shadow DOM 引用的基础设施,比如 <label for="email"> 必须对应 <input id="email">
class 是样式和行为分组的通用载体,React/Vue 的 class 绑定、BEM 命名、CSS-in-JS 都基于它id 不会报错,但会让 getElementById、querySelector('#xxx')、aria-labelledby 失效或不可预测ID 选择器的 specificity 是 100,一个 #sidebar .nav-link 就压倒了 10 个类组合(如 .container .sidebar .nav .link.active)。这不是“更强”,而是更难覆盖。
常见现象:!important 加了也没用,因为权重差跨层级——#modal .close(100+10=110)比 body .modal .close(1+10+10=21)高太多,!important 只在同级比较中生效。
!important,否则后续改样式会陷入“不断加权重”的死循环.card-title 改成 .card > .title(10+1=11 → 10+1=11,但更明确),而不是硬塞 #card .title
理论上 getElementById 是哈希查找,getElementsByClassName 是遍历,但现代浏览器优化后,差异通常低于 0.01ms。真正的问题在于容错性。
当模板动态渲染出多个 id="tooltip",document.getElementById('tooltip') 永远只拿到第一个;而 document.querySelectorAll('.tooltip') 能拿到全部,配合 forEach 或事件委托也更自然。
document.querySelector('.item-list').addEventListener(...) 比 document.getElementById('list').addEventListener(...) 更健壮——DOM 变动后仍有效document.querySelector('[data-testid="save-button"]') 或带 js- 前缀的 class(如 .js-save-btn),而非依赖 IDref(Vue)或 useRef(React),根本绕开查询逻辑不是“性能更好”或“看起来更正式”,而是场景刚性需要唯一标识。
比如表单 <label for="phone"> 必须指向 <input id="phone">;页面内跳转 <a href="#section3"> 必须有对应 <h2 id="section3">;Web Components 中 this.shadowRoot.getElementById('input') 是标准 API 调用方式。
.btn-primary 就行id="header" 当作样式钩子,结果某天要加第二个 header,整个逻辑就崩了id。