JavaScript日期异常:VanillaJS与Vue3同代码输出不一致
问题分析:JavaScript日期处理异常原因及解决办法
问题背景
已知JavaScript日期处理存在特殊性,曾考虑改用moment.js或通过PHP后端处理以避免新增依赖,但希望搞清楚当前遇到的问题:需要根据指定起始日期和月份数,生成每月递增1个月的日期数组,但使用某高赞addMonths函数时,实际每次会增加2个月;更奇怪的是,Vue3中循环内的console.log输出符合预期,但推入数组的结果却和VanillaJS一致出现异常。
复现代码
// 来源:Stack Overflow的addMonths函数 function addMonths(date, months) { let d = date.getDate(); date.setMonth(date.getMonth() + +months); if (date.getDate() !== d) { date.setDate(0); } return date; } let starting_date = "2023-03-23"; let months_left = 6; let header_months = []; for (let i = 0; i < months_left + 1; i++) { let start_date = new Date(starting_date); console.log(addMonths(start_date, i)); header_months.push(addMonths(start_date, i)); } console.log(header_months);
不同环境输出
VanillaJS输出
0: Wed Mar 22 2023 18:00:00 GMT-0600 (Mountain Daylight Time) 1: Mon May 22 2023 18:00:00 GMT-0600 (Mountain Daylight Time) 2: Sat Jul 22 2023 18:00:00 GMT-0600 (Mountain Daylight Time) 3: Fri Sep 22 2023 18:00:00 GMT-0600 (Mountain Daylight Time) 4: Wed Nov 22 2023 18:00:00 GMT-0700 (Mountain Standard Time) 5: Mon Jan 22 2024 18:00:00 GMT-0700 (Mountain Standard Time) 6: Fri Mar 22 2024 18:00:00 GMT-0600 (Mountain Daylight Time)
Vue3输出
循环内console.log结果:
Show.vue:394 Wed Mar 22 2023 18:00:00 GMT-0600 (Mountain Daylight Time) Show.vue:394 Sat Apr 22 2023 18:00:00 GMT-0600 (Mountain Daylight Time) Show.vue:394 Mon May 22 2023 18:00:00 GMT-0600 (Mountain Daylight Time) Show.vue:394 Thu Jun 22 2023 18:00:00 GMT-0600 (Mountain Daylight Time) Show.vue:394 Sat Jul 22 2023 18:00:00 GMT-0600 (Mountain Daylight Time) Show.vue:394 Tue Aug 22 2023 18:00:00 GMT-0600 (Mountain Daylight Time)
但最终header_months数组结果与VanillaJS完全一致,存在每月递增2个月的异常。
问题核心原因
- Date对象是引用类型:
addMonths函数直接修改传入的Date对象本身,而非创建并返回新的Date对象。 - 重复修改同一对象:循环中对同一个
start_date对象连续调用两次addMonths(start_date, i):- 第一次调用已将
start_date增加了i个月 - 第二次调用在已修改的
start_date基础上再次增加i个月,最终实际增加了2*i个月
- 第一次调用已将
- Vue3 console.log的误导:循环内的
console.log输出的是第一次调用后的正确结果,但推入数组的是第二次调用后的翻倍结果,导致视觉上出现“log正常、数组异常”的矛盾现象。
解决思路
方案1:修改addMonths函数,操作日期副本
修改函数,创建原日期的副本进行操作,避免修改传入的原Date对象:
function addMonths(date, months) { // 创建原日期的副本,隔离引用 const newDate = new Date(date); const originalDate = newDate.getDate(); newDate.setMonth(newDate.getMonth() + months); // 处理月末日期溢出(比如3月31日加1个月到4月30日) if (newDate.getDate() !== originalDate) { newDate.setDate(0); } return newDate; }
方案2:循环内仅调用一次addMonths
将调用结果存入变量,再同时用于日志和数组推送,避免重复修改同一对象:
for (let i = 0; i < months_left + 1; i++) { let start_date = new Date(starting_date); const currentDate = addMonths(start_date, i); console.log(currentDate); header_months.push(currentDate); }
方案3:使用更可靠的日期工具库
如果项目允许,可使用轻量的日期库(如date-fns)替代手写函数,避免原生Date API的坑:
import { addMonths } from 'date-fns'; // 示例用法 const startingDate = new Date("2023-03-23"); const headerMonths = Array.from({ length: 7 }, (_, i) => addMonths(startingDate, i));
内容的提问来源于stack exchange,提问作者Pawel K
相关产品推荐
相关产品推荐

