class和id不是随意分类名,它们决定js选中、css生效和无障碍识别;错误使用会导致逻辑失效,如classname+=重复添加、data-*命名不合法、contenteditable缺tabindex等。

class 和 id 不是“随便加的分类名”,它们直接决定 JS 能否选中、CSS 能否生效、无障碍能否识别——加错位置、写错格式,后面所有逻辑都可能失效。
为什么 class 多次拼接会出问题
动态添加 class 时用 el.className += " active" 看似简单,但会导致重复值堆积:active active active。虽然 CSS 仍能匹配,但 JS 判断 el.classList.contains("active") 会始终为 true,而 el.classList.toggle("active") 行为异常。
- 正确做法:统一用
el.classList.add("active")或el.classList.toggle("active") - 服务端模板里避免直接插值:
class="{{ user.role }}"若user.role是"admin script",会意外引入非法 class,甚至触发 XSS -
class值含空格必须加引号:<div class="btn primary">✅,<code><div class="btn" primary>❌(<code>primary被当作文本节点丢弃)data-*属性命名和读取不匹配的典型表现写
data-userId="123",JS 却读不到dataset.userId;写data-1st-place="gold",结果dataset["1stPlace"]是undefined——这不是 JS 错了,是浏览器根本不解析。- 合法命名只允许小写字母、数字、短横线(
-),不能有大写字母或下划线:data-user-id✅,data-userId❌ - 连字符自动转驼峰:
data-user-id="123"→dataset.userId,不是dataset.user-id(语法错误) - 数字开头的 key(如
data-1st-place)在 dataset 中被完全忽略,IE11 还不支持大写字母转换(data-MyKey→mykey,而非myKey) - 存对象必须手动序列化:
dataset.config = JSON.stringify(obj),取时再JSON.parse(dataset.config)
contenteditable加了却点不进去?缺的往往是tabindexcontenteditable="true"单独存在时,元素无法通过 Tab 进入、鼠标点击无响应、中文输入法打不开——这不是浏览器 bug,而是可访问性规范强制要求:必须显式声明tabindex="0"才算“键盘可达”。- 只写
contenteditable,等价于contenteditable="true",但焦点流缺失,WCAG 2.1 2.1.1 直接不通过 -
tabindex="-1"可用 JS 聚焦(el.focus()),但不在自然 tab 流中;tabindex="5"强制排序,反而破坏屏幕阅读器逻辑 - 子元素若设了
user-select: none或pointer-events: none,会拦截光标定位,出现“能聚焦但无法输入”的假象 - 最小可用组合:
<div contenteditable="true" tabindex="0" spellcheck="false">,后者还能去掉拼写下划线干扰 <h3> <code>hidden和contenteditable这类布尔属性的值无效陷阱hidden="false"依然隐藏,contenteditable="0"或contenteditable="off"全部无效——HTML 规范只认三个值:true、false、空字符串(等价于true)。- 布尔属性的存在即生效:
<div hidden> 和 <code><div hidden="anything"> 效果完全一样 <li> <code>spellcheck="false"有效,但spellcheck="0"或spellcheck="no"会被忽略 -
inert属性虽属全局,但 Chrome 122+、Firefox 125+ 才稳定支持,Safari 当前不支持,别默认它可用
真正难的不是记住哪些属性能加,而是理解每个属性背后的行为边界:一个
tabindex缺失让编辑区域不可达,一个data-命名违规让 JS 读不到数据,一个没引号的class让样式突然失效——这些都不是“差不多就行”的地方。 - 布尔属性的存在即生效:
- 合法命名只允许小写字母、数字、短横线(











