深入对比原生JavaScript Date对象与Day.js获取并格式化当前日期的代码实现,涵盖时间戳获取、标准格式输出、各时间分量提取及常见语法陷阱。
原生JS与dayjs获取当前日期的代码对比
时间:2026-11-03
浏览:308
文章目录
Date对象,无需安装任何外部依赖,但由于API设计历史悠久,格式化需手工提取各字段并做补零计算;而Day.js作为一个仅约2KB的轻量级时间库,采用与Moment.js一致的声明式链式API和不可变数据设计,大幅简化了日期获取与排版流程。本文直接围绕两种方案的代码实现展开对比,逐项拆解核心语法、常见格式化场景与高发易错边界。
一、核心对象构建与当前时间戳获取对比
无论使用原生方案还是第三方库,获取当前时间的第一步都是在运行时内存中构建对应的时间对象实例,或者直接读取当前的UNIX毫秒时间戳。
1. 原生 JavaScript 的实例创建与时间戳
原生JS使用内置的全局构造函数Date。当不传入任何参数时,new Date()会依据宿主环境的系统时钟实例化一个时间对象;若仅需要毫秒时间戳,则可以直接调用静态方法Date.now(),省去实例化对象的内存开销:
// 1. 实例化当前时间对象(包含完整原型方法)
const nowDate = new Date();
console.log(nowDate); // 输出本地时间或UTC时间字符串,例如: Sat Oct 31 2026 20:42:00 GMT+0800
// 2. 直接获取当前毫秒级时间戳(13位纯数字,性能极高)
const timestampMs = Date.now();
console.log(timestampMs); // 输出示例: 1793431379000
// 3. 从已有实例中取出毫秒时间戳
const timeFromInstance = nowDate.getTime();
console.log(timeFromInstance); // 输出: 1793431379000
2. Day.js 的实例创建与时间戳
Day.js提供了一个工厂函数dayjs()。当无参调用时,它内部会自动调用原生new Date()并封装为一个不可变的Day.js对象,同时提供链式调用的时间戳导出方法:
// 1. 实例化当前时间包装对象
const nowDayjs = dayjs();
console.log(nowDayjs); // 输出Dayjs包装对象,内部持有时间属性与原型链
// 2. 获取毫秒级时间戳(等价于 getTime())
const ms = nowDayjs.valueOf();
console.log(ms); // 输出: 1793431379000
// 3. 获取秒级时间戳(10位UNIX时间戳,原生JS需除以1000并取整)
const unixSec = nowDayjs.unix();
console.log(unixSec); // 输出: 1793431379
下方的图1展示了原生Date对象与Day.js包装对象在运行时内存布局和方法调用上的底层差异。原生Date直接暴露底层可变槽,而Day.js则构建了一层不可变的代理壳层:
二、常见日期格式化输出代码对比
获取当前日期的最终目的,大多是为了在界面上向用户展示符合阅读习惯的格式,如YYYY-MM-DD(年月日)或YYYY-MM-DD HH:mm:ss(年月日时分秒)。这是原生JS与Day.js代码量与可读性差异最显著的环节。
1. 场景一:输出 YYYY-MM-DD(如 2026-10-31)
原生 JavaScript 实现方式:由于原生Date对象没有内置形如format('YYYY-MM-DD')的函数,开发者必须逐一提取年份、月份、日期,并针对小于10的月份和日期手工调用padStart(2, '0')进行补零,再拼接字符串:
// 原生 JS 手动拼接实现
function getNativeCurrentDate() {
const now = new Date();
const year = now.getFullYear();
// getMonth() 返回 0~11,必须 +1
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
console.log(getNativeCurrentDate()); // 示例输出: "2026-10-31"
注:现代浏览器与Node.js也可使用Intl.DateTimeFormat,例如new Intl.DateTimeFormat('en-CA').format(new Date()),虽然能输出类似格式,但语法较为隐晦且对复杂定制模板支持不均。
Day.js 实现方式:Day.js直接内置了强大的词法格式化解析器,只需要传入标准令牌字符串即可:
// Day.js 声明式格式化
const currentDate = dayjs().format('YYYY-MM-DD');
console.log(currentDate); // 示例输出: "2026-10-31"
2. 场景二:输出 YYYY-MM-DD HH:mm:ss(完整日期时间)
当需求延伸到包含时、分、秒时,原生JS的代码冗余度呈线性上升,必须对时、分、秒分别执行提取和补零逻辑:
// 原生 JS 完整时分秒提取
function getNativeFullDateTime() {
const d = new Date();
const pad = (n) => String(n).padStart(2, '0');
const Y = d.getFullYear();
const M = pad(d.getMonth() + 1);
const D = pad(d.getDate());
const h = pad(d.getHours());
const m = pad(d.getMinutes());
const s = pad(d.getSeconds());
return `${Y}-${M}-${D} ${h}:${m}:${s}`;
}
console.log(getNativeFullDateTime()); // 示例输出: "2026-10-31 20:42:00"
使用Day.js时,仅需将格式化模板替换为对应的全量令牌串,调用逻辑保持单行简洁:
// Day.js 单行输出完整日期时间
const fullTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
console.log(fullTime); // 示例输出: "2026-10-31 20:42:00"
下方的图2将两者的执行步骤流水线进行了平行对照。原生JS需要经过4个阶段的手动处理,而Day.js则将复杂的格式转换封装在内部管道之中:
三、年、月、日、时、分、秒分量提取及易错陷阱
除了直接输出字符串,业务逻辑中经常需要单独获取当前的具体年份、月份、周几等数值来进行条件分支判断。这一部分是原生JavaScript的“重灾区”,存在数个广为人知的历史设计缺陷。
1. 核心分量 API 对照表
| 目标时间维度 | 原生 JS (Date) | Day.js (dayjs) | 数值范围与注意事项 |
|---|---|---|---|
| 当前年份 | date.getFullYear() |
day.year() |
例如 2026。严禁使用 getYear()(已废弃)。 |
| 当前月份 | date.getMonth() + 1 |
day.month() + 1 |
0~11(0代表1月,9代表10月)。必须手动 +1。 |
| 当月日期(几号) | date.getDate() |
day.date() |
1~31。注意方法名是 Date 而非 Day。 |
| 星期几(周几) | date.getDay() |
day.day() |
0~6(0代表周日,1代表周一,依此类推)。 |
| 当前小时 (24小时制) | date.getHours() |
day.hour() |
0~23。 |
| 当前分钟 | date.getMinutes() |
day.minute() |
0~59。 |
| 当前秒数 | date.getSeconds() |
day.second() |
0~59。 |
2. 典型易错陷阱深度剖析
原生
date.getMonth() 返回的值范围是 0 ~ 11,这是继承自早期Java Date设计(受C语言POSIX规范影响)的典型历史遗留问题。在原生JS中,获取当前10月会返回数字 9。如果忘记 + 1,日期将直接错退一个月。在Day.js中,若使用
dayjs().format('MM'),内部会自动处理成正确的带前导零月份("10");只有在直接调用 dayjs().month() 取原始数值时,才同样遵循 0~11 约定以保持与标准兼容。
getDate() 与 getDay() 的命名混淆在原生JS中,
getDate() 返回的是当月的日期(1~31号),而 getDay() 返回的是星期几(0~6)。初学者极易误认为 getDay() 是获取“今天几号”,从而引发严重的业务逻辑Bug。Day.js 将获取几号命名为
.date(),获取星期命名为 .day(),词义对应清晰,且在格式化串中使用 DD 代表日期、d 代表星期,彻底消除了混淆。
四、对象可变性与基于当前日期的计算衍生
在许多业务场景下,获取当前日期并不是终点,往往紧接着需要计算“昨天”、“明天”、“当前日期7天后”或“本月最后一天”。这时原生Date的可变性(Mutability)与Day.js的不可变性(Immutability)会产生巨大的工程行为差异。
1. 原生 Date 的就地修改(副作用风险)
原生Date实例的所有set*方法均会直接修改原对象内存。如果该实例被多处引用或作为函数参数传递,极易发生隐蔽的副作用:
const now = new Date();
// 期望获取 7 天后的日期
const future = now;
future.setDate(now.getDate() + 7);
// 致命陷阱:原本的 now 实例也被修改成了 7 天后!
console.log(now.getDate() === future.getDate()); // true
// 安全的原生做法:必须先深拷贝
const safeNow = new Date();
const safeFuture = new Date(safeNow.getTime());
safeFuture.setDate(safeFuture.getDate() + 7);
2. Day.js 的纯函数式不可变计算
Day.js在设计之初就确立了不可变(Immutable)原则。所有基于当前日期的算术运算,都会返回一个全新的Dayjs实例,原对象绝不会被改动:
const now = dayjs();
// 获取 7 天后的日期,原 now 保持不变
const future = now.add(7, 'day');
console.log(now.format('YYYY-MM-DD')); // "2026-10-31" (依旧是当前日期)
console.log(future.format('YYYY-MM-DD')); // "2026-11-07" (全新的计算实例)
// 计算今天所在月份的最后一天
const endOfMonth = now.endOf('month').format('YYYY-MM-DD');
console.log(endOfMonth); // "2026-10-31"
五、选型建议与场景决策
在实际项目架构中,究竟应当坚持零依赖使用原生JS,还是引入Day.js?可以依据以下两条清晰的判定路径进行决策:
-
优先选用原生 JS 的场景:
- 极度敏感的体积要求:例如开发独立的统计埋点SDK、轻量级CLI脚手架或嵌入式Webview页面,要求打包体积趋近于0。
- 仅需纯时间戳或标准ISO存储:只需要记录
Date.now()做接口耗时统计,或调用new Date().toISOString()传递给后端数据库。 - 仅做简单日期提取且有封装规范:项目中已有成熟的基础函数库封装了补零逻辑。
-
优先选用 Day.js 的场景:
- 中后台管理系统与业务前端:界面中充斥着各类定制化日期显示(如中文“年月日”、带星期的报表表头、倒计时)。
- 频繁的时间计算与对比:涉及“过去30天”、“跨月/跨年计算”、“两时间差判断”(
diff方法)。 - 需要国际化与时区扩展:Day.js拥有完善的插件生态(如时区插件
timezone、相对时间插件relativeTime),在维持极低包体积的前提下提供了企业级功能支持。
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。