ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Vue中获取当前日期时间全指南:Date、Moment与封装实践

Vue中获取当前日期时间全指南:Date、Moment与封装实践 做 Vue 项目这几年我几乎在每个后台管理系统里都会碰到同一个需求在页面上显示当前日期时间。有的放在顶部栏有的作为表单默认值有的要在列表页把后端返回的时间戳格式化成2025-09-11 14:20:00这种样式。而不管新人还是老手只要一遇到“获取当前日期时间”第一反应基本就是new Date()等发现原生格式化很麻烦又开始找moment。这篇文章我想把 Vue 中获取当前日期时间的完整链路讲清楚new Date()能做到什么、做不了什么moment进来之后怎么用才不乱以及项目里更稳妥的封装思路。内容适合正在写后台管理页面的 Vue 开发者也适合准备 Vue 面试时被问到“前端时间格式化怎么处理”的读者。我会尽量少说废话直接给用法、给代码、给注意事项。1. 新手最容易踩的坑在 template 里直接 new Date()1.1 为什么模板里的 Date 不响应先看一段很经典的“天真代码”template p{{ new Date() }}/p /template这段代码能跑页面上确实会显示一个时间。但这不代表它是可用的实现。Vue 的模板插值支持JavaScript表达式new Date()会当作表达式执行一次所以你在渲染结果里能看到时间。问题在于Vue 的响应式系统只能追踪响应式数据的变化而new Date()这个表达式本身没有依赖任何data里的字段Vue 不知道它什么时候会变自然也不会在下一秒主动触发重新渲染。如果你同时还有别的响应式数据发生变化比如某个loading从true变成false整个组件重新渲染了这个new Date()会顺便被重新求值变成当时的“新时间”。但只要你希望它像挂钟一样每秒走这种写法完全不合格。有个更容易混淆的情况在模板里直接调用方法比如{{ getTime() }}。方法内部返回new Date().toLocaleString()看起来比{{ new Date() }}更“正规”实际上渲染时机问题和直接写表达式是一样的。方法在每次渲染时被调用但它无法主动触发渲染。如果你的组件只有这一个时间显示没有任何其他响应式变化它就静止了。1.2 从裸 Date 对象到用户能看的字符串就算只是“页面打开时显示一次”{{ new Date() }}的输出也很有问题。不同浏览器和系统下裸Date对象转字符串的格式不完全一致通常长这样Thu Sep 11 2025 14:20:00 GMT0800 (中国标准时间)用户看到这种格式会有一种“这页面没做完”的感觉。如果换toLocaleString()输出会是浏览器本地化后的格式写法输出示例new Date()Thu Sep 11 2025 14:20:00 GMT0800 (中国标准时间)new Date().toLocaleString(zh-CN)2025/9/11 14:20:00手写formatDateTime(new Date())2025-09-11 14:20:00最后一种才是大多数后台系统想要的也是产品经理口中“统一一下时间格式”的真实含义。但原生Date不直接支持YYYY-MM-DD HH:mm:ss这种模板格式你只能自己拼。拼一次还好每个页面都拼一次就成了灾难。所以很多人开始接触moment。从这一节可以得出一个结论template里写new Date()不是不能用但只能用于一次性展示而且不推荐。真正要显示“当前时间”必须把它放进响应式数据里再配合定时器去更新。这个逻辑后面第 4 节会展开。2. 原生 Date 到底能做到什么程度2.1 常用 API 和那些必须记住的“偏移”在聊moment之前先花一点篇幅把原生Date的家底数清楚因为并不是所有场景都需要引库。方法返回值注意事项getFullYear()2025不要用getYear()它返回的是125getMonth()0-11必须手动112 月会返回 11getDate()1-31这是“号”不是“星期几”getDay()0-60 代表周日不是周一getHours()0-23下午 2 点返回 14getMinutes()0-59getSeconds()0-59getTime()毫秒时间戳等价于Date.now()toISOString()2025-09-11T06:20:00.000ZUTC 时间不是本地时间最容易出 bug 的是getMonth()。它的取值范围是0-11导致new Date(2025, 0, 1)是 1 月 1 日new Date(2025, 11, 1)才是 12 月 1 日。这个设计是早期Java风格的历史遗留直到今天也没有被纠正。很多新手第一次写日期格式化都是在本该1的地方没加结果 11 月的记录全显示成 10 月。getDay()也是个经典陷阱。它返回的星期数从 0 开始0 是周日。如果你要做“周一”到“周日”的映射需要自己写数组const weekMap [周日, 周一, 周二, 周三, 周四, 周五, 周六]; const weekText weekMap[new Date().getDay()]; // 今天周几2.2 手写一个 dateFormat替代大部分格式化需求如果只是要把日期显示成2025-09-11 14:20:00不引任何库也能做。我自己常用的工具函数长这样function padZero(n) { return String(n).padStart(2, 0); } function formatDateTime(date new Date(), template YYYY-MM-DD HH:mm:ss) { const d date instanceof Date ? date : new Date(date); const map { YYYY: d.getFullYear(), MM: padZero(d.getMonth() 1), DD: padZero(d.getDate()), HH: padZero(d.getHours()), mm: padZero(d.getMinutes()), ss: padZero(d.getSeconds()), }; return template.replace(/YYYY|MM|DD|HH|mm|ss/g, (key) String(map[key])); } console.log(formatDateTime()); // 2025-09-11 14:20:00注意padZero用到了String.prototype.padStart这是 ES2017 的标准方法现代浏览器和Node.js都支持。如果你要兼容很老的环境可以自己写function padZero(n) { return n 10 ? 0 n : n; }这个函数的核心思路是把Date对象的各个字段取出来按照模板字符串里的占位符替换。比起toLocaleString()它的输出完全可控不会因为用户系统语言不同把分隔符变成/或美式MM/DD/YYYY。2.3 原生方案解决不了的问题手写formatDateTime可以应付展示但一旦需求复杂一点原生方案就开始吃力。日期运算就是典型场景。要算“7 天后的日期”原生写法是const d new Date(); d.setDate(d.getDate() 7);setDate会改变原始对象如果后面还要用原来的日期你得先new Date()一次做拷贝。要算“两个日期相差几个自然日”得先归一化到零点再getTime()相减最后除以一天的毫秒数。代码能写但逻辑散落在业务代码里读起来很累。时区也是原生方案的痛点。后端返回一个 UTC 时间字符串前端要显示本地时间用toLocaleString能拿到本地化结果但如果你想指定显示哪个时区的时间原生Date几乎是搬起石头砸自己的脚。getTimezoneOffset()只能告诉你当前设备相对 UTC 差多少分钟要实现“同时显示纽约和北京时间”这种需求手写能写到你怀疑人生。还有一个高频需求是相对时间也就是“刚刚”“5 分钟前”“昨天 18:30”这类表达。原生Date没有任何内置方法得自己算Date.now()的差值再分支判断。所以这里我给一个比较实际的建议如果项目里只有“显示时间”这一个需求用手写formatDateTime完全够用一旦涉及日期加减、时间范围比较、相对时间、时区展示果断用库。3. moment.js 在 Vue 项目里的正确接入方式3.1 安装与全局注册Vue 2 和 Vue 3 的差异moment是老牌时间处理库API 强大文档成熟很多 2020 年之前启动的 Vue 项目都在用。安装很简单npm install moment然后是注册方式。Vue 2 项目的入口main.js里通常会这样挂到全局import moment from moment; Vue.prototype.$moment moment;这样所有组件内部都能直接this.$moment()不用每个文件都import moment from moment。它的好处是配置一次全项目统一。Vue 3 没有了Vue.prototype改用globalPropertiesimport { createApp } from vue; import App from ./App.vue; import moment from moment; const app createApp(App); app.config.globalProperties.$moment moment; app.mount(#app);在组件里用的时候Vue 3 的选项式 API 中依然可以用this.$moment()如果用的是script setup因为setup里拿不到this我更推荐直接显式 importimport moment from moment;显式 import 还有一个额外的好处利于打包工具分析依赖。全局挂载的方式虽然方便但打包时会把整个moment包含进去不利于按需优化。关于体积问题第 7 节再详细说。3.2 高频 API 清单moment的 API 很多但日常项目里真正高频的其实就那么几个。// 当前时间 this.$moment(); // 格式化 this.$moment().format(YYYY-MM-DD HH:mm:ss); // 把时间戳、Date 对象、字符串转成 moment 对象 this.$moment(1720000000000); this.$moment(new Date()); this.$moment(2025-09-11 14:20:00, YYYY-MM-DD HH:mm:ss); // 日期运算7 天后 this.$moment().add(7, days).format(YYYY-MM-DD); // 日期运算一个月前 this.$moment().subtract(1, months).format(YYYY-MM-DD); // 当天的开始和结束 this.$moment().startOf(day).format(YYYY-MM-DD HH:mm:ss); this.$moment().endOf(day).format(YYYY-MM-DD HH:mm:ss); // 两个时间相差几天 this.$moment(2025-09-11).diff(2025-09-01, days); // 相对时间 this.$moment(2025-09-11).fromNow();这里有一个新手几乎必踩的坑moment(2025-09-11 14:20:00)直接解析字符串浏览器和 moment 的解析规则可能会不一致控制台会打出Deprecation warning。正确的做法是解析时显式传入格式this.$moment(2025-09-11 14:20:00, YYYY-MM-DD HH:mm:ss);3.3 一个完整的当前时间展示组件有了moment写“当前时间”组件就变得很直接。这是一个 Vue 2 选项式 API 的例子template span classcurrent-time{{ currentTime }}/span /template script export default { name: CurrentTime, data() { return { timer: null, currentTime: , }; }, mounted() { this.updateTime(); this.timer setInterval(this.updateTime, 1000); }, beforeDestroy() { clearInterval(this.timer); }, methods: { updateTime() { this.currentTime this.$moment().format(YYYY-MM-DD HH:mm:ss); }, }, }; /script这个组件已经具备了一个可用时钟的完整要素进入页面先更新一次然后每秒更新卸载时清理定时器。注意beforeDestroy是 Vue 2 的生命周期名。如果你在 Vue 3 选项式 API 里写beforeDestroy它不会生效需要换成beforeUnmount。在script setup中则是对应的onBeforeUnmount。4. 实时更新时间的正确姿势data、computed、定时器三者怎么配合4.1 为什么必须走响应式数据前面提到{{ new Date() }}不跳动根本原因是它不在 Vue 的响应式系统里。要让时间走动唯一的方法是把“当前时间”变成响应式数据例如data() { return { now: null, }; },当now的值被修改时Vue 会重新渲染依赖它的 DOM。定时器做的事就是周期性地改nowthis.now new Date();这个模式是整个时间显示功能的核心不管用moment还是不用都绕不开。有同学会问能不能直接把格式化好的字符串存在now里也可以但更推荐存时间戳。因为时间戳是“源头”显示格式只是“派生状态”。如果你存的是格式化字符串以后想在同一页面再显示“日期”和“时间”两个不同格式就得同时维护两个字段。如果你存的是时间戳定义两个计算属性分别格式化就行逻辑更干净。4.2 setInterval 的清理和后台标签页节流问题定时器一定要清理这是内存泄漏的重灾区。组件被销毁了定时器还在一秒一秒地跑回调里还在改一个已经不存在的组件的状态控制台报错甚至页面卡顿都由此而来。所以setInterval的返回值必须存下来在恰当的时机clearInterval。除了内存泄漏还有一个容易被忽略的体验问题浏览器对后台标签页的定时器有节流机制。当用户切到其他标签页页面里的setInterval可能会降频甚至暂停切回来之后积压的回调可能连续执行好几次。如果你在回调里写的是“每次加 1 秒”那时间就会变得不对。解决办法是回调里永远重新取当前时间而不是累加updateTime() { this.now Date.now(); }这样哪怕定时器在后台被节流回到前台时时间能立刻修正。如果要求更严格还可以监听visibilitychangedocument.addEventListener(visibilitychange, () { if (document.visibilityState visible) { this.updateTime(); } });4.3 Composition API 写法与 computed 缓存Vue 3 项目现在更多用script setup同一个时钟组件的写法会变成这样template span{{ currentTime }}/span /template script setup import { ref, computed, onMounted, onBeforeUnmount } from vue; import moment from moment; const now ref(Date.now()); const currentTime computed(() moment(now.value).format(YYYY-MM-DD HH:mm:ss)); let timer null; function updateTime() { now.value Date.now(); } onMounted(() { updateTime(); timer setInterval(updateTime, 1000); }); onBeforeUnmount(() { clearInterval(timer); }); /scriptcomputed在这里的价值不只是优雅还有缓存。currentTime依赖now只有now变化时才会重新计算。如果组件在 1 秒内因为其他响应式数据变化被重新渲染但没有到定时器更新时间的节点currentTime不会重复执行moment().format()省掉一次不必要的计算。这里也顺便回答一个面试常问题computed和methods的区别是什么。在时间显示这个例子里computed的缓存特性就是最直观的答案。5. 项目级封装别让 moment 散落在每个组件里5.1 封装 dateTime 工具模块的收益在真实项目里我最不建议的写法是在每一个组件里直接moment().format(YYYY-MM-DD HH:mm:ss)。字符串格式一旦要改比如从YYYY-MM-DD HH:mm:ss改成YYYY/MM/DD HH:mm:ss你就要全局搜索替换几十个文件还容易漏。更合理的做法是把时间处理收敛到一个工具模块里。src/utils/dateTime.jsimport moment from moment; export function formatDateTime(date new Date(), format YYYY-MM-DD HH:mm:ss) { return moment(date).format(format); } export function formatDate(date new Date()) { return moment(date).format(YYYY-MM-DD); } export function getTodayRange() { const start moment().startOf(day); const end moment().endOf(day); return { start: start.valueOf(), end: end.valueOf(), }; } export function toTimestamp(date new Date()) { return moment(date).valueOf(); }组件里使用import { formatDateTime, getTodayRange } from /utils/dateTime; // 格式化表格里的时间 const createTime formatDateTime(row.createTime); // 查询“今天”的数据范围 const { start, end } getTodayRange();封装之后格式约定有了唯一入口测试也方便。更重要的是哪天团队决定把moment换成dayjs只需要改dateTime.js一个文件业务代码完全不用动。这就是好的抽象的价值。5.2 纯 JS 实现不引库也能格式化的兜底方案如果项目很小或者你不想因为一个格式化需求引一个几十 KB 的库纯 JS 的方案就够用。第 2.2 节里的formatDateTime可以直接搬用。我再补一个更完整一点的版本function padZero(n) { return String(n).padStart(2, 0); } export function formatDateTime(input new Date(), template YYYY-MM-DD HH:mm:ss) { const d input instanceof Date ? input : new Date(input); if (Number.isNaN(d.getTime())) { throw new Error(Invalid date); } const map { YYYY: d.getFullYear(), MM: padZero(d.getMonth() 1), DD: padZero(d.getDate()), HH: padZero(d.getHours()), mm: padZero(d.getMinutes()), ss: padZero(d.getSeconds()), }; return template.replace(/YYYY|MM|DD|HH|mm|ss/g, (key) String(map[key])); }这段代码对入参做了一次Date转换并校验了非法日期。当接口返回脏数据时它能让你第一时间在控制台看到报错而不是等到页面渲染才发现。5.3 统一格式约定团队协作更省心的落地经验这里分享一些非代码层面的经验。时间格式看着小实际最容易产生“公说公有理”的争论。我参与的项目会在文档里明确约定页面展示统一用YYYY-MM-DD HH:mm:ss只需要日期时用YYYY-MM-DD。传给后端的参数统一用毫秒时间戳不要在接口里传2025-09-11 14:20:00这种字符串避免前后端解析时区产生偏差。后端返回的统一用 ISO 8601 或者纯时间戳并且明确标注时区。这些约定不一定适用于所有团队但“提前约定”这件事比约定本身的内容更重要。时间出问题的时候排查成本往往是写代码成本的十倍。6. 接口联调时的时间戳和时区这些坑我基本都踩过6.1 后端返回 UTC 字符串前端怎么正确显示本地时间接口联调是时间问题的高发区。最常见的场景后端数据库存的是 UTC 时间返回给前端的是这样一串字符串2025-09-11T14:20:00.000Z尾部带Z表示这是 UTC 时间。如果你直接当普通字符串截取替换const str 2025-09-11T14:20:00.000Z; const result str.replace(T, ).split(.)[0]; // result 2025-09-11 14:20:00这看起来“对”实际上时区错了。在中国时区UTC8这个时间应该显示为22:20:00。正确做法是先用moment解析成时间对象再格式化为本地时间const timeStr 2025-09-11T14:20:00.000Z; moment(timeStr).format(YYYY-MM-DD HH:mm:ss); // 在中国时区输出 2025-09-11 22:20:00为什么显示出来会主动加 8 小时因为moment在解析带Z的字符串时知道它是 UTC 时间回传给你的是本地时区下的时间。如果你希望无论用户在哪个时区都固定显示原时区的时间可以用moment.utc()moment.utc(2025-09-11T14:20:00.000Z).format(YYYY-MM-DD HH:mm:ss); // 输出 2025-09-11 14:20:00这里要明确产品需求是“转成用户本地时间”还是“保持某时区时间”。大多数面向国内用户的后台系统用的是前者。6.2 查询“今天”的数据startOf/endOf 的优雅写法筛选“今天创建的订单”这类需求新手会这样写const start moment().format(YYYY-MM-DD) 00:00:00; const end moment().format(YYYY-MM-DD) 23:59:59;这种字符串拼接不是不能用但它隐含了“前端本地时区”的假设。如果后端期望的是 UTC 时间或者用户设备时区设置得比较随意边界就会出错。更稳妥的写法是直接用startOf和endOfconst start moment().startOf(day).valueOf(); const end moment().endOf(day).valueOf();startOf(day)拿到的是当天零点的moment对象endOf(day)拿到的是当天最后一毫秒。转成时间戳传后端后端只做范围比较不受字符串格式影响。我在联调时还会做一步额外确认和后端对齐单位是毫秒还是秒。Java 后端常用毫秒PHP 老项目可能用秒。如果前端把毫秒当成秒传给后端日期直接错到离谱。6.3 相对时间和语言包的坑评论系统、公告列表里经常要显示“3 小时前”“昨天 18:30”。moment的fromNow()和calendar()就是为这个准备的moment(2025-09-11 08:00:00).fromNow(); // 英文环境输出 6 hours ago moment(2025-09-11 08:00:00).calendar(); // 英文环境输出 Today at 8:00 AM但moment默认语言是英文直接用在中文项目里会很违和。需要手动引入中文语言包import moment from moment; import moment/locale/zh-cn; moment.locale(zh-cn);设置之后fromNow()输出就变成“6 小时前”calendar()输出“今天 08:00”。这个初始化应该在项目入口做一次而不是在组件里反复设置。7. 体积、可维护性与替代方案moment 不是唯一解7.1 moment 的两个经典问题体积大、对象可变虽然moment很好用但你也一定听过不少吐槽主要集中在两点。第一是体积。moment组件包本身加上所有语言包在未压缩打包时会是一个不小的负担。Vue 项目用 webpack 时如果你直接import moment from moment产物里大概率会包含默认语言包和部分额外逻辑。要处理语言包得在 webpack 配置里加忽略规则// vue.config.js 或 webpack 配置 const webpack require(webpack); module.exports { configureWebpack: { plugins: [ new webpack.IgnorePlugin({ resourceRegExp: /^\.\/locale$/, contextRegExp: /moment$/, }), ], }, };加了之后moment会忽略所有语言包你再按需引入moment/locale/zh-cn。第二是可变性。moment对象的方法很多会修改原对象。比如const a moment(2025-09-11); const b a.add(1, day); // a 和 b 都变成了 2025-09-12 console.log(a.format(YYYY-MM-DD), b.format(YYYY-MM-DD));add把a自己也改了。这在复杂逻辑里很容易制造隐式 bug。所以操作moment对象前有必要先.clone()一下或者用一个变量名暗示已经在用新对象。这个坑踩过的人都有印象。7.2 dayjs 做无缝替换的迁移成本如果你准备启动一个新项目又希望拥有moment的 API 体验那么dayjs是目前社区认可度比较高的替代方案。它的核心优势是API 和moment高度兼容多数代码只改 import 就能跑。体积小很多按需插件化。对象不可变add、subtract返回新对象不修改原对象。示例import dayjs from dayjs; import relativeTime from dayjs/plugin/relativeTime; import dayjs/locale/zh-cn; dayjs.extend(relativeTime); dayjs.locale(zh-cn); dayjs().format(YYYY-MM-DD HH:mm:ss); dayjs(2025-09-11T14:20:00.000Z).format(YYYY-MM-DD HH:mm:ss); dayjs(2025-09-11).fromNow();这里有一个关键点如果项目最开始就按第 5 节的方式做了统一封装迁移成本会非常低。你只需要改dateTime.js里的内部实现所有组件都不会感知到换了库。7.3 什么时候我建议不引任何时间库我必须说一句可能跟很多教程观点不太一样的话如果你的项目只是偶尔显示一个当前时间没有相对时间、没有日期运算、没有时区转换那可以不引任何时间库。手写一个formatDateTime也就十几行维护成本极低。Intl.DateTimeFormat是原生 API 里被低估的选项const formatter new Intl.DateTimeFormat(zh-CN, { year: numeric, month: 2-digit, day: 2-digit, hour: 2-digit, minute: 2-digit, second: 2-digit, hour12: false, }); formatter.format(new Date()); // 输出2025/09/11 14:20:00注意它的输出分隔符和month/day的位数受运行环境影响如果你需要严格匹配YYYY-MM-DD HH:mm:ss手写函数更可控。聊到底我想给一个非常个人的经验接手老项目时先看它已经用了什么不要因为从众就急着换启动新项目时先问一句项目里到底有没有日期运算、相对时间、时区这些需求。很多时候你的代码里只是需要一行“当前时间”那new Date()加一个自己封装的format就很好真到了要做多时区报表、算到期日、对比时间区间的时候再引入moment或dayjs也来得及。把时间处理的入口收敛到一个工具文件里远比纠结用哪个库更重要。
返回列表