JavaScript 中按日期排序并按月份分组列表项的完整实现指南

5次阅读

JavaScript 中按日期排序并按月份分组列表项的完整实现指南

本文提供一种健壮、可维护的 javaScript 方案,用于解析形如 “Monday, April 10th” 的自然语言日期字符串,对 元素进行全局时间升序排序,并自动插入月度标题实现视觉分组,无需修改原始 HTML 结构。

本文提供一种健壮、可维护的 javascript 方案,用于解析形如 “monday, april 10th” 的自然语言日期字符串,对 `

  • ` 元素进行全局时间升序排序,并自动插入月度标题实现视觉分组,无需修改原始 html 结构。

    在构建动态筛选器或日历式内容列表时,常需对含自然语言日期(如 Monday, April 10th)的 dom 元素进行精确排序 + 语义分组。由于原始 HTML 不允许添加 data-date 等属性,且日期格式不标准(含星期、序数后缀),直接使用 new Date() 易出错。本文提供一套生产就绪的解决方案:先统一解析日期,再排序,最后按月动态注入分组标题,全程操作真实 DOM 节点,保证结构与样式完整性。

    ✅ 核心思路:解析 → 排序 → 分组渲染

    关键在于将不可靠的字符串日期转化为可靠的时间戳,并避免重复解析。我们采用「预计算 + 属性挂载」策略提升性能与可读性:

    // 步骤 1:安全解析日期字符串,返回 [Date对象, 月份名] function parseDateFromText(dateStr) {   // 移除序数后缀(st/nd/rd/th)并分割:e.g. "Monday, April 10th" → ["April", "10"]   const clean = dateStr.slice(0, -2); // 去掉最后两个字符("th"等)   const parts = clean.split(/,?s+/).filter(Boolean);   const month = parts[1] || "";   const day = parts[2] || "1";    // 构造标准化日期字符串(年份固定为当前年,避免跨年歧义)   const year = new Date().getFullYear();   const isoString = `${year}-${monthToNumber(month)}-${day.padStart(2, '0')}`;    // 返回解析结果:Date 对象 + 原始月份名(用于分组)   return [new Date(isoString), month]; }  // 辅助函数:月份名转数字(避免 Intl API 兼容性问题) function monthToNumber(monthName) {   const map = {     'January': '01', 'February': '02', 'March': '03', 'April': '04',     'May': '05', 'June': '06', 'July': '07', 'August': '08',     'September': '09', 'October': '10', 'November': '11', 'December': '12'   };   return map[monthName] || '01'; }

    ✅ 执行排序与智能分组

    以下函数完成全部逻辑:遍历所有

  • ,解析日期并挂载 .__sortDate 和 .__month 属性;全局排序后,清空
      并逐个追加节点——当月份变更时,自动插入

      April

      function sortAndGroupByMonth() {   const ul = document.querySelector('.faceted-filter-group-display__list');   if (!ul) return;    const liList = Array.from(ul.querySelectorAll('.faceted-filter-group-display__list-item'));    // 预解析:为每个 li 添加私有属性(避免排序中重复解析)   liList.forEach(li => {     const textEl = li.querySelector('.faceted-filter-group-display__list-item-label-text');     if (!textEl) return;     const [dateObj, monthName] = parseDateFromText(textEl.textContent);     li.__sortDate = dateObj;     li.__month = monthName;   });    // 按日期升序排序(稳定排序,同月内保持原始相对顺序)   liList.sort((a, b) => a.__sortDate - b.__sortDate);    // 清空并重建:插入月标题 + 列表项   ul.innerHTML = '';   let currentMonth = '';    liList.forEach(li => {     // 检测月份变化,插入标题     if (li.__month !== currentMonth) {       const header = document.createElement('h3');       header.className = 'month-header';       header.textContent = li.__month;       ul.appendChild(header);       currentMonth = li.__month;     }     ul.appendChild(li);   }); }  // 启动排序(例如绑定到按钮点击或 DOMContentLoaded) document.addEventListener('DOMContentLoaded', () => {   // 可选:首次加载即执行   sortAndGroupByMonth();    // 或绑定到控制按钮:   // document.querySelector('#sort-btn').addEventListener('click', sortAndGroupByMonth); });

      ⚠️ 注意事项与最佳实践

      • 年份处理:示例中使用 new Date().getFullYear() 作为默认年份。若数据可能跨年,请确保原始 HTML 中的日期隐含有效年份,或改用更鲁棒的解析(如正则提取年份)。
      • 序数后缀兼容性:slice(0,-2) 适用于 st/nd/rd/th,但若存在 22nd、31st 等变体,建议升级为正则 /(d+)(st|nd|rd|th)/ 提取纯数字。
      • 性能优化:对数百项以上列表,可启用 DocumentFragment 批量插入(见下方优化版):
        const fragment = document.createDocumentFragment(); liList.forEach(li => {   if (li.__month !== currentMonth) {     const h3 = document.createElement('h3');     h3.textContent = li.__month;     fragment.appendChild(h3);     currentMonth = li.__month;   }   fragment.appendChild(li); }); ul.appendChild(fragment); // 单次重排,性能更优
      • 样式隔离:为避免

        影响原有布局,建议添加 CSS:

        .month-header {   margin: 1rem 0 0.5rem;   font-size: 0.9em;   font-weight: 600;   color: #666;   padding-left: 0.5rem;   border-left: 3px solid #007bff; }

      ✅ 总结

      本方案摒弃了低效的多次 DOM 查询与字符串切分,通过一次预解析 + 属性缓存,显著提升排序稳定性与执行效率;分组逻辑不依赖预定义月份对象,完全由实际数据驱动,天然支持缺失月份跳过、多月重复等边界场景。代码简洁、无外部依赖、兼容主流浏览器,可直接集成至现有项目中。

      立即学习Java免费学习笔记(深入)”;

  • text=ZqhQzanResources