「周更第1期」实用JS库推荐:Day.js

60 阅读11分钟

引言

大家好,欢迎来到第1期的JavaScript库推荐!本期为大家介绍的是 Day.js,一个轻量级、现代化的JavaScript日期处理库,专为简化日期操作而设计。

在日常开发中,我们经常遇到日期格式化、时间计算、相对时间显示等需求。传统的解决方案往往存在体积庞大(如Moment.js高达67KB)、API复杂、性能不佳等问题。Day.js 正是为了解决这些痛点而生的,它以其仅2KB的超小体积、与Moment.js兼容的API设计、优异的性能表现在日期处理库中脱颖而出,成为了现代前端开发的首选方案。

相比于其他日期库,Day.js 的核心优势在于:极致轻量(仅2KB gzipped)、API兼容(与Moment.js高度兼容)、按需加载(插件化架构)、现代化设计(不可变对象、链式调用)。这些特点使其特别适合对包体积敏感的现代Web应用。

本文将从Day.js的核心特性、实际应用、性能表现、最佳实践等多个维度进行深入分析,帮助你全面了解这个优秀的工具库。

库介绍

基本信息

主要特性

  • 🚀 极致轻量:仅2KB的超小体积,比Moment.js小97%
  • 💡 API兼容:与Moment.js高度兼容的API设计,迁移成本极低
  • 🔧 插件化架构:按需加载功能,避免代码冗余
  • 📱 不可变对象:所有操作返回新实例,避免意外修改
  • 🌍 国际化支持:支持100+语言环境
  • 高性能:优化的算法实现,性能优于传统日期库
  • 🔗 链式调用:流畅的API设计,提升开发体验
  • 📦 零依赖:无外部依赖,减少安全风险

兼容性

  • 浏览器支持:IE9+、Chrome、Firefox、Safari、Edge
  • Node.js支持:Node.js 8.0+
  • 框架兼容:完美兼容React、Vue、Angular等主流框架
  • TypeScript支持:提供完整的TypeScript类型定义

安装使用

安装方式

# npm
npm install dayjs

# yarn
yarn add dayjs

# pnpm
pnpm add dayjs

基础使用

1. 导入库

// ES6 模块导入
import dayjs from 'dayjs';

// CommonJS 导入
const dayjs = require('dayjs');

// CDN 引入
// <script src="https://cdn.jsdelivr.net/npm/dayjs"></script>

2. 基础示例

// 基础使用示例
// Day.js 提供简洁直观的API来处理日期

// 示例1:获取当前时间
const getCurrentTime = () => {
  const now = dayjs();
  const formatted = now.format('YYYY-MM-DD HH:mm:ss');
  return formatted;
};

// 示例2:解析和格式化日期
const parseAndFormat = () => {
  const date1 = dayjs('2024-01-15');
  const date2 = dayjs('01/15/2024');
  const date3 = dayjs(new Date());
  
  return {
    iso: date1.format(),
    custom: date1.format('MM/DD/YYYY'),
    chinese: date1.format('YYYY年MM月DD日')
  };
};

// 示例3:日期计算
const dateCalculation = () => {
  const today = dayjs();
  
  const nextWeek = today.add(7, 'day');
  const lastMonth = today.subtract(1, 'month');
  const startOfMonth = today.startOf('month');
  
  return {
    today: today.format('YYYY-MM-DD'),
    nextWeek: nextWeek.format('YYYY-MM-DD'),
    lastMonth: lastMonth.format('YYYY-MM-DD'),
    monthStart: startOfMonth.format('YYYY-MM-DD')
  };
};

3. 配置选项

// Day.js 支持插件扩展和全局配置
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn'; // 导入中文语言包
import relativeTime from 'dayjs/plugin/relativeTime';
import advancedFormat from 'dayjs/plugin/advancedFormat';

// 扩展插件
dayjs.extend(relativeTime);
dayjs.extend(advancedFormat);

// 设置全局语言
dayjs.locale('zh-cn');

// 使用扩展功能
const extendedExample = () => {
  const now = dayjs();
  
  return {
    relative: now.fromNow(), // 相对时间:"几秒前"
    quarter: now.format('Q'), // 季度:"1"
    week: now.format('w') // 周数:"3"
  };
};

实际应用

应用场景1:博客文章时间显示

在博客或新闻网站中,我们需要显示文章的发布时间,要求根据时间间隔显示不同格式(如"刚刚"、"3小时前"、"昨天"、"2024年1月15日")。

// 完整的博客时间显示解决方案
// 包含智能时间格式化和错误处理

const formatArticleTime = (publishTime) => {
  try {
    const publishDate = dayjs(publishTime);
    const now = dayjs();
    
    const hoursDiff = now.diff(publishDate, 'hour');
    const daysDiff = now.diff(publishDate, 'day');
    
    let displayTime;
    if (hoursDiff < 1) {
      displayTime = '刚刚';
    } else if (hoursDiff < 24) {
      displayTime = publishDate.fromNow();
    } else if (daysDiff < 7) {
      displayTime = publishDate.fromNow();
    } else if (daysDiff < 365) {
      displayTime = publishDate.format('MM月DD日');
    } else {
      displayTime = publishDate.format('YYYY年MM月DD日');
    }
    
    return {
      smart: displayTime,
      relative: publishDate.fromNow(),
      detailed: hoursDiff < 24 
        ? publishDate.format('今天 HH:mm')
        : publishDate.format('MM月DD日 HH:mm'),
      full: publishDate.format('YYYY年MM月DD日 HH:mm:ss'),
      iso: publishDate.toISOString(),
      timestamp: publishDate.valueOf()
    };
  } catch (error) {
    console.error('时间格式化失败:', error);
    return {
      smart: '时间未知',
      relative: '时间未知',
      detailed: '时间未知',
      full: '时间未知',
      iso: null,
      timestamp: null
    };
  }
};

// 使用示例
const articleData = {
  title: '学习 Day.js 的最佳实践',
  publishTime: dayjs().subtract(4, 'hour').toISOString(),
  content: '...'
};

const timeInfo = formatArticleTime(articleData.publishTime);
console.log(timeInfo.smart); // "4小时前"

应用场景2:高级日期范围选择器

在数据分析或报表系统中,我们需要实现一个功能强大的日期范围选择器,支持预设范围、自定义范围、统计信息等功能。

// 高级日期范围选择器实现
// 支持多种预设范围和详细统计

class AdvancedDateRangeSelector {
  static getPresetRange(preset, options = {}) {
    const { timezone = 'local', includeTime = false } = options;
    const today = dayjs();
    
    const timeMethod = includeTime ? 'endOf' : 'startOf';
    const endTimeUnit = includeTime ? 'day' : 'day';
    
    const presets = {
      today: {
        start: today.startOf('day'),
        end: today[timeMethod](endTimeUnit)
      },
      yesterday: {
        start: today.subtract(1, 'day').startOf('day'),
        end: today.subtract(1, 'day')[timeMethod](endTimeUnit)
      },
      last7days: {
        start: today.subtract(6, 'day').startOf('day'),
        end: today[timeMethod](endTimeUnit)
      },
      last30days: {
        start: today.subtract(29, 'day').startOf('day'),
        end: today[timeMethod](endTimeUnit)
      },
      thisWeek: {
        start: today.startOf('week'),
        end: today[timeMethod]('week')
      },
      thisMonth: {
        start: today.startOf('month'),
        end: today[timeMethod]('month')
      }
    };
    
    return presets[preset] || presets.today;
  }
  
  static isDateInRange(date, range, precision = 'day') {
    const targetDate = dayjs(date);
    return targetDate.isBetween(range.start, range.end, precision, '[]');
  }
  
  static getRangeStats(range) {
    const { start, end } = range;
    
    return {
      totalDays: end.diff(start, 'day') + 1,
      totalHours: end.diff(start, 'hour') + 1,
      totalMinutes: end.diff(start, 'minute') + 1,
      totalWeeks: Math.ceil(end.diff(start, 'day') / 7),
      totalMonths: end.diff(start, 'month'),
      description: `${start.format('YYYY-MM-DD')}${end.format('YYYY-MM-DD')}`,
      shortDescription: `${start.format('MM/DD')} - ${end.format('MM/DD')}`,
      crossMonth: start.month() !== end.month(),
      crossYear: start.year() !== end.year(),
      crossWeek: start.week() !== end.week(),
      startQuarter: start.quarter(),
      endQuarter: end.quarter(),
      startWeek: start.week(),
      endWeek: end.week(),
      workingDays: this.calculateWorkingDays(start, end),
      rangeType: this.determineRangeType(start, end)
    };
  }
  
  static calculateWorkingDays(start, end) {
    let workingDays = 0;
    let current = start.clone();
    
    while (current.isSameOrBefore(end, 'day')) {
      if (current.day() !== 0 && current.day() !== 6) {
        workingDays++;
      }
      current = current.add(1, 'day');
    }
    
    return workingDays;
  }
  
  static determineRangeType(start, end) {
    const days = end.diff(start, 'day') + 1;
    
    if (days === 1) return 'single-day';
    if (days <= 7) return 'week-range';
    if (days <= 31) return 'month-range';
    if (days <= 92) return 'quarter-range';
    return 'long-range';
  }
  
  static generateDateList(range, format = 'YYYY-MM-DD') {
    const dates = [];
    let current = range.start.clone();
    
    while (current.isSameOrBefore(range.end, 'day')) {
      dates.push(current.format(format));
      current = current.add(1, 'day');
    }
    
    return dates;
  }
}

// 使用示例
const last30DaysRange = AdvancedDateRangeSelector.getPresetRange('last30days');
const stats = AdvancedDateRangeSelector.getRangeStats(last30DaysRange);

console.log('最近30天统计:', {
  描述: stats.description,
  总天数: stats.totalDays,
  工作日: stats.workingDays,
  范围类型: stats.rangeType
});

优缺点分析

优点 ✅

  • 极致轻量:2KB的体积相比Moment.js的67KB,减少了97%的包大小,显著提升页面加载速度
  • API兼容性:与Moment.js高度兼容的API设计,迁移成本极低,学习曲线平缓
  • 插件化架构:按需加载功能模块,避免代码冗余,支持Tree Shaking优化
  • 不可变设计:所有操作返回新实例,避免意外修改原始数据,提高代码安全性
  • 现代化特性:支持ES6+语法、TypeScript、模块化导入等现代开发特性
  • 高性能表现:优化的算法实现,在大量日期操作场景下性能优异
  • 活跃维护:持续更新维护,社区活跃,问题响应及时

缺点 ❌

  • 功能相对精简:相比Moment.js,某些高级功能需要额外插件支持,可能增加配置复杂度
  • 插件依赖:复杂功能需要手动引入插件,对新手来说可能存在学习成本
  • 生态系统:虽然快速发展,但相比Moment.js的生态系统仍有差距,某些第三方集成可能需要额外适配

与同类库对比

特性Day.jsMoment.jsdate-fnsLuxon
包大小2KB67KB13KB15KB
API兼容性-
不可变性
Tree Shaking
TypeScript
国际化
维护状态活跃停止活跃活跃

最佳实践

开发建议

1. 性能优化技巧

// 最佳实践示例1:复用Day.js实例
const optimizedUsage = () => {
  // 推荐:复用基础日期实例
  const baseDate = dayjs('2024-01-15');
  
  const results = [
    baseDate.add(1, 'day'),
    baseDate.subtract(1, 'week'),
    baseDate.startOf('month')
  ];
  
  return results.map(date => date.format('YYYY-MM-DD'));
};

// 避免的做法:重复创建相同的日期实例
const inefficientUsage = () => {
  // 不推荐:每次都重新解析相同的日期字符串
  return [
    dayjs('2024-01-15').add(1, 'day').format('YYYY-MM-DD'),
    dayjs('2024-01-15').subtract(1, 'week').format('YYYY-MM-DD'),
    dayjs('2024-01-15').startOf('month').format('YYYY-MM-DD')
  ];
};

2. 错误处理策略

// 完善的错误处理示例
const robustDateHandling = (dateInput) => {
  try {
    // 输入验证
    if (!dateInput) {
      throw new Error('日期输入不能为空');
    }
    
    const date = dayjs(dateInput);
    
    // 有效性检查
    if (!date.isValid()) {
      throw new Error(`无效的日期格式: ${dateInput}`);
    }
    
    // 业务逻辑验证
    if (date.isAfter(dayjs())) {
      throw new Error('日期不能是未来时间');
    }
    
    return {
      success: true,
      date: date,
      formatted: date.format('YYYY-MM-DD HH:mm:ss')
    };
    
  } catch (error) {
    console.error('日期处理错误:', error.message);
    
    return {
      success: false,
      error: error.message,
      fallback: dayjs().format('YYYY-MM-DD HH:mm:ss')
    };
  }
};

3. 内存管理

// 内存优化示例
const memoryEfficientUsage = () => {
  // Day.js 对象是不可变的,无需手动清理
  // 但要注意避免创建过多的临时对象
  
  // 推荐:批量处理
  const dates = ['2024-01-01', '2024-01-02', '2024-01-03'];
  const processedDates = dates.map(dateStr => {
    const date = dayjs(dateStr);
    return {
      original: dateStr,
      formatted: date.format('YYYY年MM月DD日'),
      timestamp: date.valueOf()
    };
  });
  
  return processedDates;
};

常见陷阱

  • ⚠️ 时区处理陷阱:Day.js 默认使用本地时区,跨时区应用需要使用timezone插件并明确指定时区
  • ⚠️ 插件加载顺序:某些插件有依赖关系,需要按正确顺序加载,如timezone插件依赖utc插件
  • ⚠️ 格式化字符串:使用自定义格式时要注意转义特殊字符,避免格式化结果异常

进阶用法

高级特性

1. 自定义解析格式

// 高级用法示例1:自定义解析格式
import customParseFormat from 'dayjs/plugin/customParseFormat';
dayjs.extend(customParseFormat);

const advancedParsing = () => {
  // 解析特定格式的日期字符串
  const date1 = dayjs('12-25-1995', 'MM-DD-YYYY');
  const date2 = dayjs('1995/12/25 10:30:45', 'YYYY/MM/DD HH:mm:ss');
  const date3 = dayjs('Q1 2024', '[Q]Q YYYY');
  
  // 严格模式解析
  const strictDate = dayjs('2024-01-32', 'YYYY-MM-DD', true);
  
  return {
    date1: date1.format(),
    date2: date2.format(),
    date3: date3.format(),
    isStrictValid: strictDate.isValid() // false,因为1月没有32日
  };
};

2. 持续时间处理

// 高级用法示例2:持续时间处理
import duration from 'dayjs/plugin/duration';
dayjs.extend(duration);

const advancedDuration = () => {
  // 创建持续时间对象
  const duration1 = dayjs.duration(2, 'hours');
  const duration2 = dayjs.duration({ hours: 2, minutes: 30, seconds: 45 });
  const duration3 = dayjs.duration('PT2H30M45S'); // ISO 8601格式
  
  // 持续时间计算
  const totalMinutes = duration2.asMinutes();
  const humanized = duration2.humanize();
  
  // 计算两个时间点之间的持续时间
  const start = dayjs('2024-01-15 09:00:00');
  const end = dayjs('2024-01-15 17:30:00');
  const workDuration = dayjs.duration(end.diff(start));
  
  return {
    duration1: duration1.humanize(),
    duration2: duration2.humanize(),
    totalMinutes,
    humanized,
    workHours: workDuration.asHours(),
    workDurationText: workDuration.humanize()
  };
};

自定义扩展

// 如何扩展Day.js功能
const customExtension = () => {
  // 添加自定义方法
  dayjs.extend((option, dayjsClass, dayjsFactory) => {
    // 添加获取工作日的方法
    dayjsClass.prototype.isWorkingDay = function() {
      const day = this.day();
      return day !== 0 && day !== 6; // 0是周日,6是周六
    };
    
    // 添加获取下一个工作日的方法
    dayjsClass.prototype.nextWorkingDay = function() {
      let next = this.add(1, 'day');
      while (!next.isWorkingDay()) {
        next = next.add(1, 'day');
      }
      return next;
    };
  });
  
  // 使用自定义方法
  const today = dayjs();
  return {
    isWorkingDay: today.isWorkingDay(),
    nextWorkingDay: today.nextWorkingDay().format('YYYY-MM-DD')
  };
};

工具集成

  • 构建工具:支持Webpack、Vite、Rollup等现代构建工具的Tree Shaking优化
  • 测试框架:可与Jest、Mocha等测试框架无缝集成,支持时间Mock
  • 开发工具:提供VS Code插件、ESLint规则等开发辅助工具

故障排除

常见问题

Q1: 时区显示不正确

问题描述:在不同时区环境下,时间显示结果与预期不符

解决方案

// 使用timezone插件处理时区问题
import timezone from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc';

dayjs.extend(utc);
dayjs.extend(timezone);

// 明确指定时区
const timezoneFix = () => {
  const utcTime = dayjs.utc('2024-01-15 10:00:00');
  const beijingTime = utcTime.tz('Asia/Shanghai');
  const newYorkTime = utcTime.tz('America/New_York');
  
  return {
    utc: utcTime.format(),
    beijing: beijingTime.format(),
    newYork: newYorkTime.format()
  };
};

Q2: 插件加载失败

问题描述:引入插件后功能不生效或报错

解决方案

// 确保正确的插件加载顺序
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc'; // 先加载utc
import timezone from 'dayjs/plugin/timezone'; // 再加载timezone

// 按正确顺序扩展
dayjs.extend(utc);
dayjs.extend(timezone);

// 验证插件是否正确加载
const pluginCheck = () => {
  try {
    const result = dayjs().tz('Asia/Shanghai');
    return { success: true, result: result.format() };
  } catch (error) {
    return { success: false, error: error.message };
  }
};

调试技巧

// 调试代码示例
const debugExample = () => {
  // 检查Day.js版本和配置
  console.log('Day.js版本:', dayjs.version || 'unknown');
  console.log('当前语言:', dayjs.locale());
  
  // 调试日期解析
  const debugDate = (input, format) => {
    const date = format ? dayjs(input, format) : dayjs(input);
    return {
      input,
      format,
      isValid: date.isValid(),
      parsed: date.isValid() ? date.format() : 'Invalid Date',
      timestamp: date.isValid() ? date.valueOf() : null
    };
  };
  
  console.log('调试结果:', debugDate('2024-01-15'));
};

性能问题诊断

  • 检查点1:确认是否正确使用了Tree Shaking,避免引入不必要的插件
  • 检查点2:在大量日期操作场景下,考虑批量处理和对象复用
  • 检查点3:检查是否存在内存泄漏,特别是在长时间运行的应用中

总结

Day.js 是一个设计精良、性能优异的JavaScript日期处理库,特别适合现代Web开发中对包体积敏感的场景。它的极致轻量、API兼容性、插件化架构使其在日期处理库中表现出色,是Moment.js的完美替代方案。

推荐指数:⭐⭐⭐⭐⭐ (5/5)

适合人群

  • ✅ 注重包体积优化的前端开发者
  • ✅ 需要从Moment.js迁移的项目团队
  • ✅ 现代化Web应用和移动端应用开发者
  • ✅ 需要高性能日期处理的数据可视化项目

不适合场景

  • ❌ 需要复杂时区计算的企业级应用(建议使用Luxon)
  • ❌ 对包体积不敏感且已深度集成Moment.js的遗留项目

学习建议

  1. 入门阶段:从基础的日期创建、格式化开始,熟悉链式调用API
  2. 进阶阶段:学习插件系统,掌握国际化、时区处理等高级功能
  3. 实战应用:在实际项目中应用,积累最佳实践和性能优化经验

相关资源


如果你觉得这篇文章对你有帮助,欢迎点赞、收藏和分享。如果你有其他想了解的JavaScript库,也欢迎在评论区留言告诉我!


本文是「掘金周更」系列的第1期,每周为大家推荐一个实用的JavaScript第三方库。关注我,不错过每一期精彩内容!