前端日期處理,為什么不要用 Date 構(gòu)造函數(shù)?
在 JavaScript 的世界里,時(shí)間與日期處理是一個(gè)繞不開的話題。從顯示文章發(fā)布時(shí)間,到計(jì)算活動(dòng)倒計(jì)時(shí),再到處理復(fù)雜的時(shí)區(qū)轉(zhuǎn)換,我們總會(huì)與日期打交道。而 JS 的內(nèi)置 Date 對(duì)象,似乎是理所當(dāng)然的第一選擇。
然而,如果我們?cè)弧叭掌诳偸遣钜惶臁?、“本地和服?wù)器時(shí)間對(duì)不上”、“月份為什么從0開始”等問(wèn)題折磨過(guò),那么我們可能已經(jīng)體會(huì)到了原生 Date 對(duì)象的“險(xiǎn)惡”。

一、Date 對(duì)象的“三宗罪”
1. 不可靠的字符串解析
這是 Date 對(duì)象最致命、最廣為人知的缺陷。new Date(dateString) 的行為在不同瀏覽器和不同日期格式下,表現(xiàn)得像一個(gè)捉摸不定的“渣男”。
看這個(gè)經(jīng)典的例子:
// 格式一:YYYY-MM-DD
const date1 = new Date('2025-07-15');
// 在多數(shù)現(xiàn)代瀏覽器中,這會(huì)被解析為 UTC 時(shí)間的零點(diǎn):
// "Tue Jul 15 2025 08:00:00 GMT+0800 (中國(guó)標(biāo)準(zhǔn)時(shí)間)"
// 格式二:YYYY/MM/DD
const date2 = new Date('2025/07/15');
// 這通常會(huì)被解析為本地時(shí)間的零點(diǎn):
// "Tue Jul 15 2025 00:00:00 GMT+0800 (中國(guó)標(biāo)準(zhǔn)時(shí)間)"發(fā)現(xiàn)問(wèn)題了嗎?
僅僅是分隔符 - 和 / 的區(qū)別,new Date() 的解析策略就完全不同。前者(YYYY-MM-DD)被當(dāng)作 UTC 時(shí)間,而后者(YYYY/MM/DD)被當(dāng)作本地時(shí)間。
這在處理只有日期的字符串時(shí)是災(zāi)難性的。假設(shè)后端傳來(lái)一個(gè) 2025-07-15,代表某人的生日。我們用 new Date() 解析后,如果用戶在西半球,獲取日期時(shí)可能會(huì)得到 2025/07/15,憑空少了一天!這種因時(shí)區(qū)差異導(dǎo)致的不確定性,是許多線上 Bug 的根源。
結(jié)論:永遠(yuǎn)不要相信 new Date(dateString) 能穩(wěn)定、跨瀏覽器地解析我們傳入的字符串。
2. 對(duì)象的可變性(Mutability)
Date 對(duì)象是可變的(mutable)。這意味著一旦創(chuàng)建,它的值可以被任意修改。這在復(fù)雜的業(yè)務(wù)邏輯中,很容易導(dǎo)致難以追蹤的副作用。
想象一個(gè)場(chǎng)景:

在上面的函數(shù)中,我們本意是想根據(jù) today 計(jì)算出 tomorrow,但由于直接修改了 today 對(duì)象,導(dǎo)致原始的 today 變量也被污染了。如果 today 在代碼的其他地方還需要使用,問(wèn)題就大了。
一個(gè)健壯的日期處理方式應(yīng)該是不可變的(immutable),即任何操作都返回一個(gè)新的日期對(duì)象,而不是修改原始對(duì)象。
3. API 設(shè)計(jì)
Date 對(duì)象的 API 設(shè)計(jì)充滿了各種反直覺:
- 月份從 0 開始:getMonth() 返回 0 代表一月,11 代表十二月。這是新手最常犯的錯(cuò)誤。new Date(2025, 7, 15) 創(chuàng)建的是八月二十六日,而不是七月
- 獲取年份的方法不統(tǒng)一:雖然現(xiàn)在我們都用 getFullYear(),但歷史上還存在一個(gè) getYear(),它在某些瀏覽器和年份下返回的是“年份減去1900”的結(jié)果
- 格式化能力為零:想把日期格式化成 YYYY-MM-DD HH:mm:ss?對(duì)不起,原生 Date 沒有提供直接的方法。我們必須手動(dòng) getFullYear(), getMonth()+1, getDate()… 然后自己拼字符串,還要處理數(shù)字前補(bǔ)零的問(wèn)題
- 復(fù)雜的日期計(jì)算:計(jì)算“30天后”或者“下個(gè)月的今天”?我們需要小心翼翼地使用 setDate() 和 setMonth(),并處理好跨月份、跨年份的邊界情況
這些設(shè)計(jì)缺陷大大降低了開發(fā)效率,并增加了出錯(cuò)的可能性。
二、正確的姿勢(shì):擁抱現(xiàn)代日期庫(kù)
既然原生 Date 如此不堪,我們?cè)撛趺崔k?答案很簡(jiǎn)單:使用一個(gè)成熟、可靠的第三方日期庫(kù)。
這并非“重復(fù)造輪子”,而是站在巨人的肩膀上,讓我們專注于業(yè)務(wù)邏輯,而不是和底層的怪癖作斗爭(zhēng)。
目前社區(qū)主流的選擇有:
1. Day.js (強(qiáng)烈推薦)
優(yōu)點(diǎn):體積小巧(壓縮后僅 2KB),API 設(shè)計(jì)與曾經(jīng)的王者 Moment.js 極其相似,學(xué)習(xí)成本低。它支持鏈?zhǔn)秸{(diào)用,代碼寫起來(lái)非常流暢。
特點(diǎn):默認(rèn)不可變(需要插件支持,但強(qiáng)烈建議使用)、功能通過(guò)插件體系擴(kuò)展(按需加載)。
看看用 Day.js 如何解決上面的問(wèn)題:
import dayjs from 'dayjs';
// 1. 可靠的解析
const date = dayjs('2025-07-15'); // 無(wú)論什么格式,解析行為都穩(wěn)定一致
// 2. 不可變性
const today = dayjs();
const tomorrow = today.add(1, 'day'); // .add() 返回一個(gè)新的 dayjs 對(duì)象
console.log(today.format()); // 原始對(duì)象不變
console.log(tomorrow.format()); // 新的對(duì)象
// 3. 優(yōu)雅的 API
// 格式化
console.log(dayjs().format('YYYY-MM-DD HH:mm:ss')); // "2025-07-15 15:30:00"
// 獲取月份 (從 1 開始)
console.log(dayjs().month() + 1);
// 計(jì)算
console.log(dayjs().add(7, 'day').format('YYYY-MM-DD')); // 7天后
console.log(dayjs().subtract(1, 'month').format('YYYY-MM-DD')); // 1個(gè)月前代碼是不是瞬間變得清晰、健壯、且易于維護(hù)了?
2. 未來(lái)的希望:Temporal API
值得一提的是,JavaScript 自身也在進(jìn)化。新的 Temporal API 旨在從根本上取代 Date 對(duì)象,提供一個(gè)全新的、設(shè)計(jì)精良的日期/時(shí)間處理方案。它內(nèi)置了不可變性、無(wú)歧義的 API 和完善的時(shí)區(qū)支持。
雖然目前還需要 Polyfill 才能使用,但它代表了 JS 日期處理的未來(lái)。
三、什么時(shí)候可以用 new Date()?
說(shuō)了這么多,是不是原生 Date 就一無(wú)是處了?也不是。在一些極其簡(jiǎn)單的場(chǎng)景下,它仍然可用:獲取當(dāng)前時(shí)間戳、傳遞給日期庫(kù)、非關(guān)鍵性、無(wú)解析需求的場(chǎng)景。
但對(duì)于任何需要解析后端返回的日期字符串、進(jìn)行日期計(jì)算、或者需要格式化顯示的業(yè)務(wù)場(chǎng)景,建議借助第三方庫(kù)。
這幾 KB 的庫(kù)體積,換來(lái)的是代碼的穩(wěn)定性、可維護(hù)性,以及讓我們從日期處理的泥潭中解放出來(lái)的寶貴時(shí)間。





























