在JavaScript中获取当前月份第一天最稳健的做法是利用 new Date(now.getFullYear(), now.getMonth(), 1) 构造新实例。本文详细剖析实现代码、时分秒归零处理、容易混淆的月度索引与原地修改副作用,助你写出健壮的日期计算代码。
JS怎么获取当前月份的第一天
时间:2026-11-11
浏览:94
文章目录
在 JavaScript 中获取当前月份的第一天,最推荐且健壮的做法是使用 new Date(now.getFullYear(), now.getMonth(), 1) 构造一个新实例。这种写法不仅语法直接,而且不会修改原始日期对象,同时会自动将时、分、秒与毫秒全部初始化为当天的零点起点(00:00:00.000)。
在日常的前端与后端 Node.js 开发中,按月份统计报表、筛选时间范围或初始化日历控件时,定位当前月份的第一天是非常高频的需求。JavaScript 原生的 Date 对象提供了两种截然不同的处理思路:一种是基于年份与月份构造全新实例,另一种是直接就地修改现有实例。理解这两者的差异,是避免各类隐蔽时间 Bug 的关键。
一、两种实现路径与底层对象行为对比
处理日期对象时,首要关注的是操作是否具有“副作用(Mutation)”。JavaScript 中的 Date 是可变对象(Mutable Object),如果直接调用修改方法,可能会影响到程序中正在使用该对象的其他逻辑。
我们来看两种主要的实现方式:
- 构造新实例法(推荐):通过
now.getFullYear()与now.getMonth()提取出年和月,配合常数1传入new Date()构造函数。整个过程属于纯计算,原有对象不受任何干扰。 - 就地修改法(需警惕):直接在现有对象上调用
now.setDate(1)。这会直接改写原对象底层的内部时间戳,属于破坏性变更。
从上图可以清晰地看到:如果你的业务场景只需要快速得到首日,并参与后续的范围对比或展示,构造全新实例是最安全的选择。如果把现有的 now 实例直接 setDate(1),那么在后面的业务逻辑中如果再次引用 now,它的日期就已经被悄悄改变成了 1 号,容易诱发难以定位的逻辑错误。
二、标准实现方案:基于构造函数的纯函数
下面是生产环境中最推荐使用的封装函数。该函数支持传入自定义的基准日期;若不传参,则默认使用当前系统时间:
/**
* 获取指定日期所在月份的第一天
* @param {Date} [date=new Date()] 基准日期,默认为当前时间
* @returns {Date} 当月第一天且时分秒为 00:00:00.000 的新 Date 实例
*/
function getFirstDayOfMonth(date = new Date()) {
return new Date(date.getFullYear(), date.getMonth(), 1);
}
// 使用示例
const firstDay = getFirstDayOfMonth();
console.log("当前月份第一天:", firstDay);
// 示例输出:Sun Nov 01 2026 00:00:00 GMT+0800 (中国标准时间)
该写法的操作步骤可以拆解为以下四个关键环节:
每个步骤的具体作用如下:
- 第 1 步:捕获时间起点 —— 无论是读取当前设备时间
new Date()还是接收函数传参,先确定好时间计算的基准对象。 - 第 2 步:提取年份与月份 —— 调用
getFullYear()获取四位整型年份(如 2026),调用getMonth()获取当前月份在 JS 内部的索引值(0 代表 1 月,1 代表 2 月,以此类推)。 - 第 3 步:锁定首日数值 —— 在
new Date(year, month, date)构造函数的第三个参数显式传入数值1,将日期锚定在当月的第 1 天。 - 第 4 步:时分秒默认归零 —— 当向
new Date()传入年、月、日这 3 个参数时,JavaScript 规范规定其时、分、秒、毫秒参数若未提供,将自动默认为0。因此该实例恰好对齐在当天的00:00:00.000,非常适合作为时间范围查询的开端。
三、核心概念辨析:易混淆的月份索引与字符串拼接
在处理 JavaScript 月份时,初学者和部分有经验的开发者经常容易产生两个代表性的困惑与误区:
1. “getMonth() 是从 0 开始的,传给构造函数时要不要加 1?”
答案是:绝对不要加 1。
很多人记忆里都有“JS月份是 0 到 11,日常月份要加 1”的概念。这种说法本身没错,但它仅适用于将月份呈现给人看,或者格式化输出字符串时。而在使用 new Date(year, monthIndex, date) 构造函数时,该构造函数自身接收的月份参数同样遵循 0 ~ 11 的索引规范!
如上图所示:
- 当前如果是 11 月,
now.getMonth()返回的数值是10。 new Date(2026, 10, 1)接收到10后,内部解析对应的正是“第 11 个月”,即 11 月 1 日。- 如果在构造时误写成了
new Date(now.getFullYear(), now.getMonth() + 1, 1),那么传入的参数变成了11,结果就会变成 12 月 1 日,直接跳到了下个月!
"2026-11-01")时,才需要执行 getMonth() + 1。传给原生构造参数时,无需任何额外计算,直接直通传递即可。
2. “用模板字符串拼接成 'YYYY-MM-01' 再 new Date() 靠谱吗?”
有些开发者喜欢使用字符串拼接:
// 不推荐的写法:跨环境时区隐患
const y = now.getFullYear();
const m = String(now.getMonth() + 1).padStart(2, '0');
const badFirstDay = new Date(`${y}-${m}-01`);
这种写法存在严重的跨浏览器时区差异风险。根据 ECMAScript 规范,解析形如 "YYYY-MM-DD" 的 ISO 8601 字符串时,很多现代运行环境会默认将其当作 UTC(协调世界时)零点 解析,然后再转换为本地时间。例如在东八区(UTC+8)的浏览器中,new Date("2026-11-01") 可能会被解析为 2026-11-01 08:00:00,而在某些西区环境中甚至可能变成前一天的晚上,导致天数产生偏离。因此,始终推荐使用多参数数值构造法,而非解析字符串。
四、备用方案:使用 setDate(1) 时的安全写法
如果你手头已经有一个 Date 对象,且业务上确实倾向于使用 setDate(1),那么必须先克隆对象,并手动调用 setHours() 抹平当前时刻残留的时分秒:
function getFirstDayViaSetDate(originalDate = new Date()) {
// 第一步:基于时间戳克隆新对象,避免修改外部传进来的原始引用
const dateCopy = new Date(originalDate.getTime());
// 第二步:将“日”设置为 1 号
dateCopy.setDate(1);
// 第三步:将时、分、秒、毫秒显式归零
dateCopy.setHours(0, 0, 0, 0);
return dateCopy;
}
const safeFirstDay = getFirstDayViaSetDate();
console.log("克隆后安全设置:", safeFirstDay);
为什么第三步必不可少?因为 originalDate 实例化时默认携带了当前执行代码时的具体时间点(如 14:32:45.678)。如果仅仅调用 dateCopy.setDate(1),那么它代表的是“1号下午两点三十二分”,在做闭区间查询(如 orderDate >= firstDay)时,会漏掉 1 号上午的所有数据!
五、常见业务场景:格式化为 YYYY-MM-DD 字符串
在实际的前端表单、下拉框以及后端 API 请求参数中,我们往往不需要 Date 实例,而是需要标准化的日历字符串 "YYYY-MM-01"。针对该需求,可以使用 padStart 补齐双位:
/**
* 获取当前月份第一天的标准日期字符串 (YYYY-MM-01)
* @param {Date} [date=new Date()]
* @returns {string} 格式化字符串,如 "2026-11-01"
*/
function getFirstDayString(date = new Date()) {
const year = date.getFullYear();
// 注意:此处用于人类阅读与通用格式,必须 + 1,并用 padStart 补齐前导 0
const month = String(date.getMonth() + 1).padStart(2, '0');
return `${year}-${month}-01`;
}
console.log("接口参数格式:", getFirstDayString());
// 示例输出:"2026-11-01"
这种纯字符串组合方式高效直观,既满足了双位数标准规范,又完全避开了浏览器对于字符串时间戳解析的时区歧义。
六、总结与选型速查
为了让不同场景下的选型更加明确,下表整理了各种实现方案的适用场景与优劣势:
| 实现方式 | 核心代码 | 是否改变原对象 | 时分秒状态 | 推荐使用场景 |
|---|---|---|---|---|
| 构造法(首选) | new Date(y, m, 1) |
否(安全纯函数) | 自动归零(00:00:00) | 日期范围过滤、日历计算、组件初始化 |
| 克隆修改法 | copy.setDate(1); copy.setHours(0,0,0,0) |
否(基于克隆对象) | 需手动显式归零 | 在现有时间管道流中对对象进行链式变更 |
| 文本格式化法 | `${y}-${m_pad}-01` |
无对象变更 | 纯日期文本 | 接口请求传参、URL查询参数、表单初始文本 |
| 直接修改法(反模式) | date.setDate(1) |
是(产生副作用) | 保留执行时刻残留时间 | 不推荐在任何生产代码中使用 |
归结起来,获取当前月份第一天最稳健的心智模型就是:直接使用 new Date(now.getFullYear(), now.getMonth(), 1) 重新构造。它兼顾了安全性、跨浏览器一致性以及开箱即用的时间零点标准化,是编写现代 JavaScript 日期逻辑的最佳选择。
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。