data-* 属性可替代状态变量,用 dataset api 统一管理 dom 状态,避免变量与 dom 脱节;contenteditable + spellcheck 适用于免表单校验的直接编辑;hidden 属性比 display: none 更语义化且影响可访问性;inert 属性可彻底禁用交互区域,但需注意浏览器兼容性。

data-* 属性如何替代状态变量
很多场景下,你其实不需要在 JavaScript 里维护一个 isExpanded 变量来控制折叠状态——DOM 元素自己就能记。用 data-expanded 直接存状态,读写都靠 dataset API,逻辑更轻、更直观。
常见错误是手动同步变量和 DOM:一边改 isExpanded = true,一边又写 el.classList.add('active'),稍不注意就脱节。用 data-expanded 后,状态只有一处源头。
-
el.dataset.expanded = 'true'写入(自动转字符串) -
el.dataset.expanded === 'true'读取,注意返回始终是字符串 - 配合 CSS 选择器:
[data-expanded="true"] .content { max-height: 500px; } - 避免用
data-expanded="1"或data-expanded(无值),统一用"true"/"false"显式语义
contenteditable + spellcheck 如何绕过输入框校验逻辑
当你要让用户直接编辑一段文本,又不想引入 <input> 或 <textarea></textarea> 的表单生命周期(比如失去焦点才触发验证),contenteditable 是更干净的解法。搭配 spellcheck="false" 还能关掉干扰性下划线。
关键点在于:它不触发 change 事件,但你可以监听 input —— 而且这个事件在任意内容变更(包括粘贴、撤销、格式化)时都会触发,比 blur 更及时。
- 必须显式设置
tabindex="0"才能获得焦点和键盘响应 -
input事件中用el.innerText或el.textContent读纯文本;用el.innerHTML读带标签结构(注意 XSS 风险) - 不要监听
keydown来做防输入——会漏掉右键粘贴、拖放、语音输入等路径 - 若需限制长度,优先用
el.innerText.length 判断,而不是截断 <code>innerHTML,否则可能破坏 HTML 结构
hidden 属性比 display: none 更适合条件渲染控制
用 hidden 替代 style.display = 'none' 不只是为了语义——它真正影响的是可访问性(AT 工具会跳过 hidden 元素)和浏览器行为(比如 hidden 元素不会被 querySelectorAll(':scope *') 匹配到,但 display: none 仍会)。
最常踩的坑是误以为 hidden 和 CSS 类一样可以随意 toggle:它没有过渡动画支持,也不能被 CSS transition 捕获。要加淡入淡出,得额外用 opacity + visibility 组合。
-
el.hidden = true等价于el.setAttribute('hidden', '');设为false则移除属性 - 不能用
el.style.hidden = 'true'—— 这是无效的,hidden是 HTML 属性,不是 CSS 属性 - 与
aria-hidden="true"不同:hidden是强制隐藏+禁用交互,aria-hidden仅对读屏器生效 - 服务端渲染时,
hidden属性会被原样输出,比 JS 控制 class 更可靠
使用 inert 属性临时禁用整块交互区域
当你需要“灰掉”一个弹窗背后的页面,或者临时屏蔽某个表单区域(比如提交中),inert 是比遍历所有子元素加 pointer-events: none 和 tabindex="-1" 更彻底的方案。它让元素及其后代完全不可聚焦、不可点击、不被 AT 访问,且无需手动管理子节点。
目前 inert 在 Chrome 105+、Edge 105+、Firefox 111+ 原生支持;Safari 尚未支持,需用 polyfill。别试图用 disabled 伪替代——它只对表单控件有效,对 <div> 或自定义组件无效。<ul>
<li>
<code>el.inert = true 启用,el.inert = false 关闭(注意不是布尔字符串)
<dialog></dialog> 元素,打开时浏览器自动将 backdrop 设为 inert,不用手写inert 会阻断所有事件冒泡,如果父级设了 inert,子级即使显式设 inert=false 也无效document.querySelector('*:not([inert])') 快速定位当前可交互区域全局属性不是语法糖,它们把 DOM 本身变成状态容器和行为开关。用得越准,JS 逻辑就越薄——但前提是清楚每个属性的边界:比如 hidden 不触发重排,inert 不兼容旧 Safari,data-* 的键名自动转驼峰这些细节,一不留神就成 bug 温床。











