先记住这个答案
Date 对象内部以 UTC 毫秒时间戳表示时间。当构造参数非法时,时间戳被设为 NaN,此时该 Date 即无效。getTime() 原样返回该时间戳,因此可用 Number.isNaN(date.getTime()) 判断。直接比较 date 永远为 truthy,而 NaN 与自身也不相等,所以 date === NaN 失效。另外,isNaN(date) 会先转换类型,但 Date 的 valueOf 返回时间戳,所以它也可行;不过推荐使用 Number.isNaN 避免隐式转换。此法同样适用于需要区分时间戳 0 与无效值的情况。
- 无效 Date 的 getTime() 返回 NaN
- 用 Number.isNaN 检测最可靠
- 直接比较或检查真值均无效
Date 内部时间戳与 invalid 的表示原理
ECMAScript 规范规定,Date 对象的核心是一个毫秒级时间值(time value),该值表示自 1970-01-01T00:00:00Z 起经过的毫秒数。当构造函数接收非法字符串、超出范围的数字或 undefined 时,这个时间值会被设为特殊的 NaN。于是该 Date 对象进入 invalid 状态,任何基于时间值的方法如 getTime()、getMonth() 等都会返回 NaN 或表现出异常。
为什么不能直接比较两个 Date?因为相等操作符比较的是对象的引用,即使两个 Date 都无效,它们也是不同的对象,不会相等;而且 NaN 是唯一不等于自身的值,所以 date.getTime() === NaN 永远为 false。正确的方法是检查 getTime() 的返回结果,用 Number.isNaN 精确判断,因为它不会把非数字参数强制转换,也不会误判像 null 这样能被转为 0 的值。
const invalid = new Date('not-a-date');
const valid = new Date(Date.UTC(1970, 0, 1));
console.log(invalid.getTime()); // NaN
console.log(Number.isNaN(invalid.getTime())); // true
console.log(valid.getTime()); // 0
console.log(Number.isNaN(valid.getTime())); // false查看输出与解释
NaN
true
0
false通过 getTime 返回的时间戳是否为 NaN 来判断 Date 是否有效。输出中 NaN 和 0 都是数值,但只有 invalid 的 getTime 是 NaN。
表单输入日期字符串时的有效性拦截
在 Web 表单中,用户输入生日字符串,后端需要校验。我们先用 new Date(input) 生成对象,但不同浏览器对 '2020-13-45' 或 'not a date' 的解析结果不同,甚至可能调整到邻近合法日期。因此必须显式检查有效性。处理时先调用 d.getTime(),若结果为 NaN,则阻断提交并显示错误消息。
更进一步,用 Number.isNaN 而非 isNaN 可避免对时间戳 0 的误判。时间戳 0 表示 1970-01-01T00:00:00Z,是完全合法的日期。在某些业务中,用户可能输入 '1970-01-01',这时 getTime() 返回 0,如果写成 if (!date.getTime()) 就会错误地认为是无效。因此必须只依据 NaN 判定,而不是真值判断。
有效边界与参数陷阱
需要注意 getTime() 只能由 Date 实例调用。如果变量不是 Date 或为 null/undefined,直接调用会抛出 TypeError。所以通用检测应先判断类型,例如使用 Object.prototype.toString.call(value) === '[object Date]',随后再调用方法。另外,Date 最大有效时间约 ±8.64e15 毫秒,即 ±273,790 年,超出此范围的构造函数会生成 invalid Date,此时检测同样适用。
另一个边界是解析字符串的行为因环境而异。规范仅要求支持 ISO 8601 格式中的 YYYY-MM-DDTHH:mm:ss.sssZ 及其变体,但其他格式由实现决定,可能意外成功或被当作无效。所以对于用户输入,应尽量使用标准化格式或手动拆解验证,不要依赖 new Date() 自动解析。检测只是最后一道防线,而非唯一手段。
容易答错的地方
- 用 if (date) 判断有效性
- Date 对象即使无效也是真值,如
new Date('bad')的布尔值为 true,所以if (date)永远进入真分支。必须检查getTime()是否为 NaN 才能确定无效。 - 用 date === NaN 判断
NaN是唯一不等于自身的值,因此任何x === NaN都是 false。即使date.getTime()返回 NaN,也必须用Number.isNaN(date.getTime())来检查,不能直接与 NaN 比较。
面试官还会怎么问?
如何区分时间戳为 0 的有效日期与无效日期?
区分方式很简单:有效日期的 getTime() 返回一个数字(包括 0),无效日期返回 NaN。因此只需用 Number.isNaN(date.getTime()),若为 false 则为有效,否则无效。时间戳为 0 是有效日期的一种,不应特殊对待。
如果传入字符串为 'Invalid Date',会怎样?
new Date('Invalid Date') 通常解析失败,得到无效 Date,其 getTime() 为 NaN,但 toString() 可能也是 'Invalid Date'。但某些环境可能产生自定义行为,最稳妥仍以时间戳为准。
为什么推荐 Number.isNaN 而不是全局 isNaN?
全局 isNaN 会先对参数进行强制类型转换,例如 isNaN('') 为 false。如果直接对 Date 对象调用 isNaN(date),Date 的 valueOf 会返回时间戳,所以通常也正确,但若传入其他对象则可能误判。使用 Number.isNaN 可避免隐式转换,更严格。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。