数据属性与语义化 HTML(Data Attributes and Semantic HTML)
一、data-* 属性
1.1 基本用法
data-* 属性允许在 HTML 元素上存储额外的数据,供 JavaScript 读取和使用。
<div id="user" data-user-id="123" data-role="admin">
张三
</div>
const element = document.getElementById('user');
// 读取 data 属性
console.log(element.dataset.userId); // "123"
console.log(element.dataset.role); // "admin"
// 设置 data 属性
element.dataset.userId = '456';
element.dataset.newAttr = 'value';
// 删除 data 属性
delete element.dataset.role;
1.2 命名转换规则
data-* 属性的命名在 dataset 对象中有特定的转换规则:
<!-- HTML: kebab-case -->
<div data-user-id="123" data-max-zoom-level="10">
<!-- JavaScript: camelCase -->
element.dataset.userId // "123"
element.dataset.maxZoomLevel // "10"
1.3 注意事项
// ❌ 不能直接用点运算符访问
element.dataset.user-id // 语法错误
// ✅ 使用方括号或 camelCase
element.dataset['user-id']
element.dataset.userId
// ⚠️ 以数字开头的属性名会变化
<div data-1st-place="winner">
element.dataset['1stPlace'] // "winner"
二、data-* 的正确应用
2.1 存储结构化数据
<!-- 存储产品信息 -->
<div class="product"
data-product-id="P001"
data-price="299.00"
data-currency="CNY"
data-stock="100">
<h3>产品名称</h3>
<p>¥299.00</p>
</div>
<!-- 存储配置选项 -->
<button
data-action="delete"
data-item-id="123"
data-confirm="确定要删除吗?">
删除
</button>
// 产品卡片点击处理
document.querySelectorAll('.product').forEach(card => {
card.addEventListener('click', () => {
const { productId, price, currency, stock } = card.dataset;
console.log(`Product ${productId}: ${currency}${price}, Stock: ${stock}`);
});
});
// 确认删除
document.querySelector('[data-action="delete"]').addEventListener('click', (e) => {
const { itemId, confirm: message } = e.target.dataset;
if (confirm(message)) {
deleteItem(itemId);
}
});
2.2 组件状态存储
// 标签页组件
class TabComponent {
constructor(container) {
this.container = container;
this.tabs = container.querySelectorAll('[data-tab]');
this.panels = container.querySelectorAll('[data-tab-panel]');
this.init();
}
init() {
this.tabs.forEach(tab => {
tab.addEventListener('click', () => {
this.activate(tab.dataset.tab);
});
});
}
activate(tabId) {
// 移除所有 active 状态
this.tabs.forEach(t => delete t.dataset.active);
this.panels.forEach(p => delete p.dataset.active);
// 设置 active 状态
this.container.querySelector(`[data-tab="${tabId}"]`).dataset.active = '';
this.container.querySelector(`[data-tab-panel="${tabId}"]`).dataset.active = '';
}
}
2.3 国际化文本存储
<button data-i18n-key="button.submit" data-i18n-params='{"name": "John"}'>
Submit
</button>
// 获取翻译
function t(key, params = {}) {
const element = document.querySelector(`[data-i18n-key="${key}"]`);
let text = translations[key] || key;
// 替换参数
Object.entries(params).forEach(([k, v]) => {
text = text.replace(`{${k}}`, v);
});
return text;
}
三、语义化 HTML
3.1 语义元素
<!-- 导航 -->
<nav aria-label="主导航">
<ul>
<li><a href="/">首页</a></li>
<li><a href="/about">关于</a></li>
</ul>
</nav>
<!-- 主要内容 -->
<main>
<article>
<header>
<h1>文章标题</h1>
<time datetime="2024-01-15">2024年1月15日</time>
</header>
<section>
<p>正文内容...</p>
</section>
<footer>
<address>联系作者</address>
</footer>
</article>
</main>
<!-- 侧边栏 -->
<aside>
<nav aria-label="页面导航">
<ul>
<li><a href="#section1">第一节</a></li>
<li><a href="#section2">第二节</a></li>
</ul>
</nav>
</aside>
<!-- 页脚 -->
<footer>
<p>© 2024 公司名称</p>
</footer>
3.2 语义化的好处
- 可访问性:屏幕阅读器可以正确解析页面结构
- SEO:搜索引擎更好地理解页面内容
- 代码可维护性:开发者更容易理解代码意图
- 跨设备兼容:不同设备可以正确渲染
3.3 常见语义错误
<!-- ❌ 错误:使用 div 做按钮 -->
<div onclick="submit()">提交</div>
<!-- ✅ 正确:使用 button -->
<button type="submit">提交</button>
<!-- ❌ 错误:标题层级跳跃 -->
<h1>一级标题</h1>
<h3>三级标题</h3>
<!-- ✅ 正确:连续层级 -->
<h1>一级标题</h1>
<h2>二级标题</h2>
<h3>三级标题</h3>
<!-- ❌ 错误:强调位置错误 -->
<p><div>重要信息</div></p>
<!-- ✅ 正确:正确嵌套 -->
<p><strong>重要信息</strong></p>
四、data-* 与其他存储方案对比
4.1 存储位置选择
| 方案 | 适用场景 | 容量 | 持久性 |
|---|---|---|---|
| data-* | 少量静态数据 | 无限制 | DOM 存活期间 |
| data-* + dataset | 组件状态 | 无限制 | DOM 存活期间 |
| localStorage | 需要持久化 | ~5-10MB | 永久(直到清除) |
| sessionStorage | 当前会话 | ~5-10MB | 标签页关闭 |
| JavaScript 变量 | 运行时数据 | 无限制 | 函数作用域 |
4.2 data-* 的限制
// ⚠️ data 属性不适合存储:
// 1. 敏感数据(用户可查看和修改)
const userId = element.dataset.userId; // 任何人都可以在 DevTools 中看到
// 2. 大量数据(影响 DOM 性能)
element.dataset.hugeData = largeObject; // data 属性应该是字符串
// 3. 需要频繁更新的数据(每次修改都触发重排)
// 应该使用 CSS 类或 JavaScript 变量
五、最佳实践
5.1 命名规范
<!-- ✅ 推荐:使用语义化的 kebab-case -->
<div data-user-profile data-loading-state>
<!-- ❌ 不推荐:过于简写 -->
<div data-up d-ls>
<!-- ✅ 推荐:带有前缀避免冲突 -->
<div data-modal-id="123" data-modal-visible="true">
5.2 组件模式
// 将组件逻辑封装
class Component {
constructor(element) {
this.element = element;
this.config = {
// 从 data 属性读取配置
theme: element.dataset.theme || 'light',
size: element.dataset.size || 'medium',
};
this.state = {
// 运行时状态存储在 dataset 中
active: false,
};
}
updateState(key, value) {
this.state[key] = value;
this.element.dataset.state = JSON.stringify(this.state);
}
}