前端时间函数使用指南与常见陷阱解析
时间函数这事儿,我踩过太多坑了
做移动端开发,时间处理几乎是绕不开的。用户要显示“3分钟前”,后台给的是 ISO 8601 字符串;本地要缓存数据,得用时间戳判断是否过期;动画要平滑,还得靠 requestAnimationFrame 配合时间差……但真正让我头疼的,不是业务逻辑,而是到底该用哪个时间函数方案。
原生 Date?moment.js?dayjs?还是自己手写?我折腾过好几个项目,从电商到社交,从 H5 到 React Native,踩过坑、翻过车,今天就来聊聊这几个主流方案的实际体验——不讲理论,只说实战。
谁更灵活?谁更省事?
先说结论:现在我基本只用 dayjs,除非项目特别老或者性能极其敏感。别急着喷,听我慢慢道来。
最早那会儿,大家都用原生 Date。代码写起来像这样:
const now = new Date();
const past = new Date('2024-01-01T10:00:00Z');
const diff = now - past; // 毫秒差
看起来挺干净,但一到格式化就崩溃了。想输出“YYYY-MM-DD HH:mm”?得自己拼字符串,还要处理时区、补零、月份从0开始这些反人类设计。更别说解析各种格式的时间字符串了,稍有不慎就返回 Invalid Date。我曾经在一个 H5 活动页里,因为用户手机时区设置异常,导致整个倒计时错乱,线上事故,半夜被叫醒修 bug。
后来有了 moment.js,简直是救星。链式调用、格式化一行搞定、国际化支持好,代码清爽多了:
import moment from 'moment';
const formatted = moment('2024-01-01T10:00:00Z').format('YYYY-MM-DD HH:mm');
但问题也来了:**体积太大**。gzip 后还有 70KB+,对移动端首屏加载是致命伤。我有个项目引入后,Lighthouse 性能分直接掉 15 分。而且它不可变(immutable)的设计在某些场景反而麻烦,比如频繁修改时间对象时,得不断 clone,内存开销不小。
于是 dayjs 出现了。它 API 和 moment 几乎一致,但体积只有 2KB(gzip 后)。我试了下,迁移成本极低,几乎改个 import 就行:
import dayjs from 'dayjs';
const formatted = dayjs('2024-01-01T10:00:00Z').format('YYYY-MM-DD HH:mm');
插件机制也够用,比如相对时间(“3分钟前”):
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(relativeTime);
console.log(dayjs().subtract(3, 'minute').fromNow()); // "3分钟前"
这里注意我踩过好几次坑:**relativeTime 插件默认是英文的**!中文得自己配 locale,或者用社区版。我现在的做法是封装一个工具函数,统一处理:
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.locale('zh-cn');
dayjs.extend(relativeTime);
export const formatRelativeTime = (date) => {
return dayjs(date).fromNow();
};
性能对比:差距比我想象的大
有人会说:“不就几 KB 吗?现在手机都快,无所谓。”但实际跑起来,差别真不小。我在一个列表页做了测试:渲染 100 条带时间的动态,每条都要格式化和计算相对时间。
- 原生 Date 手写格式化:最快,但代码丑且易错
- moment.js:平均耗时 120ms,页面明显卡顿
- dayjs:平均耗时 35ms,流畅
更关键的是内存占用。moment 的每个实例都携带大量 locale 数据,而 dayjs 默认只加载需要的部分。在低端安卓机上,moment 容易触发 GC,导致掉帧。我亲眼见过一个瀑布流页面,用 moment 时滚动卡成 PPT,换成 dayjs 后丝滑如德芙。
当然,如果你只是偶尔格式化一两个时间,比如页面顶部的“最后更新时间”,那原生 Date 也够用。但一旦涉及列表、实时更新、动画同步,我还是推荐用成熟库。
我的选型逻辑
我现在选型就看三点:
- 项目是否新:新项目无脑 dayjs
- 是否需要复杂时区处理:如果涉及全球用户,可能得上 Luxon 或 date-fns-tz,但 90% 的国内项目不需要
- 团队是否熟悉:如果老项目已经在用 moment,且没性能问题,那就别折腾
特别提醒:**千万别在移动端用 moment 作为新项目的基础库**。我见过实习生直接 npm install moment,结果上线后被 QA 报“页面加载慢”,查了半天才发现是它。
另外,date-fns 也是个选项,函数式风格,tree-shakable 做得更好。但它的 API 不如 dayjs 直观,比如格式化要传 format 字符串,而 dayjs 是方法链。我试过一次,写起来总觉得别扭,最后还是切回 dayjs 了。
至于原生 Date?除非是超轻量级的小程序或嵌入式 H5,否则我不碰。那些“不用第三方库”的洁癖,在时间处理这种细节密集的场景下,只会让你加班更多。
核心代码就这几行
最后贴个我项目里常用的封装,亲测有效:
// utils/time.js
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
import relativeTime from 'dayjs/plugin/relativeTime';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
dayjs.locale('zh-cn');
dayjs.extend(relativeTime);
dayjs.extend(utc);
dayjs.extend(timezone);
// 格式化为标准时间字符串
export const formatStandard = (date) => {
return dayjs(date).format('YYYY-MM-DD HH:mm');
};
// 相对时间(3分钟前)
export const formatRelative = (date) => {
return dayjs(date).fromNow();
};
// 处理 UTC 时间(假设后端返回的是 UTC)
export const formatFromUTC = (utcString) => {
return dayjs.utc(utcString).local().format('YYYY-MM-DD HH:mm');
};
用的时候直接 import 调用就行,不用每次重复配置。这个封装我已经在三个项目里复用了,稳得很。
以上是我踩坑后的总结,希望对你有帮助
时间函数看着简单,实则暗坑无数。选对工具能省下大把 debug 时间。我现在的策略很明确:dayjs 为主,原生 Date 为辅,moment.js 已列入黑名单(除非维护老项目)。
当然,技术没有银弹。如果你有更优的实现方式,或者在特殊场景下有更好的方案,欢迎评论区交流——说不定下次我就改主意了。
