
本文介绍如何通过 data-filter 属性与 querySelectorAll + forEach 避免重复操作 DOM 元素,用声明式方式替代硬编码的 class 切换,大幅提升代码可维护性与扩展性。
本文介绍如何通过 `data-filter` 属性与 `queryselectorall` + `foreach` 避免重复操作 dom 元素,用声明式方式替代硬编码的 class 切换,大幅提升代码可维护性与扩展性。
在前端开发中,面对多个同类元素(如 8 个食物卡片)需按类别(早餐、午餐、全部)动态显示/隐藏时,逐一手动调用 classList.add() 和 classList.remove() 不仅冗长易错,更严重违背 DRY(Don’t Repeat Yourself)原则。真正的“干净代码”不在于行数少,而在于抽象合理、职责清晰、易于扩展。
✅ 推荐方案:基于 data-filter 的声明式筛选
核心思路是将筛选逻辑从“硬编码元素引用”升级为“语义化数据驱动”:
- 为每个筛选按钮添加 data-filter="breakfast" 等语义化标识;
- 为每个食物卡片添加 data-filter="breakfast,lunch"(支持多标签);
- 使用统一选择器批量操作,通过 dataset.filter 匹配决定显隐。
? 示例代码(精简健壮版)
<!-- 筛选按钮组 --> <div class="filter-buttons"> <button class="active" data-filter="all">全部</button> <button data-filter="breakfast">早餐</button> <button data-filter="lunch">午餐</button> <button data-filter="drinks">饮品</button> </div> <!-- 食物卡片容器 --> <div class="filter"> <div data-filter="breakfast">燕麦粥</div> <div data-filter="breakfast,lunch">三明治</div> <div data-filter="drinks">橙汁</div> <div data-filter="lunch">牛肉意面</div> <div data-filter="drinks,breakfast">酸奶杯</div> <div data-filter="lunch">沙拉碗</div> </div>
/* 默认隐藏非 active 元素 */
.filter > div:not(.active) {
display: none;
}
/* 按钮激活态样式 */
.filter-buttons button.active {
background-color: #007bff;
color: white;
border-color: #007bff;
}
// 统一事件绑定与筛选逻辑
document.querySelectorAll('.filter-buttons button').forEach(button => {
button.addEventListener('click', function() {
// 1. 更新按钮激活状态
document.querySelectorAll('.filter-buttons button')
.forEach(btn => btn.classList.remove('active'));
this.classList.add('active');
// 2. 获取当前筛选类型
const filterType = this.dataset.filter;
// 3. 批量处理所有食物卡片
document.querySelectorAll('.filter > div').forEach(card => {
if (filterType === 'all') {
card.classList.add('active');
} else {
// 支持多值匹配(如 data-filter="breakfast,lunch")
const tags = card.dataset.filter?.split(',').map(t => t.trim()) || [];
card.classList.toggle('active', tags.includes(filterType));
}
});
});
});
⚠️ 关键注意事项
- 空值防护:使用 card.dataset.filter?.split(...) 避免 undefined 报错;
- 空格兼容:.map(t => t.trim()) 确保 data-filter="breakfast, lunch" 中的空格不影响匹配;
- CSS 优先级:.filter > div:not(.active) 依赖 display: none,确保无其他样式覆盖;
- 可扩展性:新增筛选项只需添加带 data-filter 的按钮和对应卡片标签,无需修改 JS 逻辑。
? 进阶提示
若需支持多选(如同时显示早餐+饮品),可改用数组存储激活类型,并用 some() 替代 includes();若卡片数量极大,可考虑 DocumentFragment 或虚拟滚动优化性能——但对 8 个元素而言,当前方案已足够高效、清晰且符合工程最佳实践。
干净的代码,始于对数据与行为的合理解耦。











