本文介绍一种基于 data-filter 属性与现代 DOM API 的声明式前端过滤方案,通过统一管理按钮状态和元素可见性,彻底告别为每个元素单独增删 active/hidden 类的冗余代码,实现真正清爽、可维护的过滤逻辑。
在构建多类别内容筛选功能时(例如一个需要按“早餐”、“午餐”、“饮品”分类展示食物卡片的页面),许多开发者一开始会采用最直接的方式:为每个需要显示或隐藏的元素,逐一调用 classList.add() 和 classList.remove()。这种方法虽然直观,但代码会迅速变得冗长且难以维护,更关键的是,它违背了“单一职责”和“可扩展性”等现代前端开发的基本原则。今天,我们就来重构这套 JavaScript 过滤逻辑,实现一个真正干净、可复用的 DOM 操作方案。
核心思路:声明式数据驱动 + 批量 DOM 操作
核心转变在于:我们不再手动“指挥”每一个元素该做什么,而是建立清晰的规则,让 JavaScript 批量执行。具体来说:
- 为每个过滤按钮添加
data-filter="xxx"属性,用它明确这个按钮的过滤意图。 - 为每个内容项也添加
data-filter属性,比如data-filter="breakfast"或data-filter="breakfast,lunch"(后者表示该项同时属于“早餐”和“午餐”两个分类)。 - 利用
document.querySelectorAll()一次性获取所有相关元素,再配合forEach()和classList.toggle()同步更新它们的状态,实现批量操作。
示例代码(完整可运行)
先来看看基础的 HTML 结构:
燕麦杯
三明治
意面
橙汁
酸奶果昔
冰咖啡
然后是控制显示/隐藏的核心 CSS:
/* 默认隐藏非 active 元素 */
.filter > div:not(.active) {
display: none;
}
/* 按钮高亮样式 */
.filter-buttons button.active {
border-bottom: 2px solid #007bff;
font-weight: bold;
}
最后是驱动一切的 JavaScript 逻辑:
// 统一绑定按钮事件
document.querySelectorAll('.filter-buttons button').forEach(button => {
button.addEventListener('click', function () {
// 1. 清除所有按钮的 active 状态
document.querySelectorAll('.filter-buttons button').forEach(btn =>
btn.classList.remove('active')
);
// 2. 当前按钮设为 active
this.classList.add('active');
// 3. 获取当前筛选类型
const filterType = this.dataset.filter;
// 4. 批量处理所有内容项
document.querySelectorAll('.filter > div').forEach(item => {
if (filterType === 'all') {
item.classList.add('active');
} else {
// 支持多值匹配:如 data-filter="breakfast,lunch"
const itemFilters = item.dataset.filter?.split(',') || [];
item.classList.toggle('active', itemFilters.includes(filterType));
}
});
});
});
注意事项与最佳实践
为了让这个方案更健壮、更专业,有几个细节值得关注:
- 健壮性增强:代码中使用了可选链操作符(
item.dataset.filter?.split(',')),这能有效防止某个内容项未设置data-filter属性时导致的运行时错误,提升 JavaScript 过滤的稳定性。 - 性能友好:
querySelectorAll返回的是一个静态的 NodeList,非常适合在当前上下文中进行一次性遍历,避免了反复查询 DOM 带来的性能损耗,尤其适合内容项较多的筛选场景。 - 可扩展性强:这是本方案最大的优势。未来如果需要增加一个新的分类(比如“甜点”),你只需要在 HTML 中添加一个带
data-filter="dessert"的按钮,以及为相关的内容项加上这个标签即可,完全无需修改 JavaScript 的核心逻辑,实现真正的代码解耦。 - CSS 解耦:元素的显示与隐藏完全由
.active这个类控制。这意味着,如果你后续想用透明度(opacity)、变形(transform)或者 CSS 动画来实现更炫酷的过渡效果,只需要修改 CSS 规则,JavaScript 代码可以保持不变,便于后期维护与样式升级。 - 无障碍友好:为了提升网站的可访问性,建议在切换按钮状态时,同步更新其
aria-pressed属性(例如:this.setAttribute('aria-pressed', 'true')),这能帮助屏幕阅读器等辅助技术更好地理解按钮的当前状态,符合 WCAG 标准。
总结
用 data-* 属性来承载业务语义,用 forEach 和 toggle 来替代重复的类名增删操作,这套组合拳不仅能显著减少代码量,更重要的是,它极大地提升了代码的逻辑清晰度和可维护性。这种声明式的过滤模式具有普适性,完全可以应用到商品列表筛选、博客标签过滤、仪表盘模块切换等任何“多态筛选”的场景中。说到底,真正的“干净代码”,其衡量标准并非行数的多少,而在于意图是否明确、变化是否局部、逻辑是否易于推演和维护。
