new Date()引发的血案

14,305 阅读4分钟

src=http___hbimg.b0.upaiyun.com_e347f36b660959525fdf01f85b7a4d63f70f1251389b5-KmRYO4_fw658&refer=http___hbimg.b0.upaiyun.jpeg

  • 背景

    • 距离上线就剩一天了,晚上临下班的时候,我们测试小姐姐火急火燎的找我来了,日期插件出问题啦,顿时虎躯一震,感觉距离下班时间又远了一步,这个插件是从其他项目直接拿过来的,已经在线上运行很久了,怎么还会有问题呢。
    • 先让测试小姐姐莫急,然后了解了一下问题,原来是插件初始化是3月份,点击下一月的时候直接跳转至5月了,初听一脸懵逼,还有这种神奇的bug,果然bug之多,无奇不有。
  • 排查

    • 开始一顿排查,最终发现是new Date()的问题,因为默认日期是3月31号,所以new Date()的时间是2021-3-31,问题就出在了这个31上边。
  • 原理

    • 创建一个 JavaScript Date 实例,该实例呈现时间中的某个时刻。Date 对象则基于 Unix Time Stamp,即自1970年1月1日(UTC)起经过的毫秒数。

    • Date()构造函数有四种基本形式

      • new Date();  //如果没有提供参数,那么新创建的Date对象表示实例化时刻的日期和时间。
        new Date(value);  //一个 Unix 时间戳(Unix Time Stamp),它是一个整数值,表示自1970年1月1日00:00:00 UTC(the Unix epoch)以来的毫秒数,忽略了闰秒。请注意大多数 Unix 时间戳功能仅精确到最接近的秒
        new Date(dateString); //表示日期的字符串值。该字符串应该能被 Date.parse() 正确方法识别
        new Date(year, monthIndex [, day [, hours [, minutes [, seconds [, milliseconds]]]]]);//当至少提供了年份与月份时,这一形式的 Date() 返回的 Date 对象中的每一个成员都来自下列参数。没有提供的成员将使用最小可能值(对日期为1,其他为0)。
        //参数monthIndex 是从“0”开始计算的,这就意味着一月份为“0”,十二月份为“11”。
        
    • 而此时问题就出在了 new Date(year, monthIndex, day)上边,new Date(2021,3,31)得出的时间竟然是(Sat May 01 2021 00:00:00 GMT+0800 (中国标准时间))2021年5月1号。

    • 注意 参数monthIndex 是从“0”开始计算的,这就意味着一月份为“0”,十二月份为“11”。

    • 首先我们要知道,使用new Date(2021,3,31)这样的格式来传入参数的时候,会将月份自动加1,得到真实月份,即传入的是3月份,则得到的是4月份。

    • **注意:**当Date作为构造函数调用并传入多个参数时,如果数值大于合理范围时(如月份为 13 或者分钟数为 70),相邻的数值会被调整。比如 new Date(2013, 13, 1)等于new Date(2014, 1, 1),它们都表示日期2014-02-01(注意月份是从0开始的)。其他数值也是类似,new Date(2013, 2, 1, 0, 70)等于new Date(2013, 2, 1, 1, 10),都表示同一个时间:2013-03-01T01:10:00。(来自MDN)

    • 所以当new Date(2021,3,31)时,由于3月份是31天而4月份并没有31号,所以数值大于了合理范围,相邻的数值被调整为了5月1号。

  • 解决方案

    • 最简单的就是调整参数格式,即new Date(YYYY,MM,DD)改为new Date(YYYY-MM-DD),但是这样的格式在我的项目代码中改动太多,不适合我当前的情况。

    • 既然是和每月的天数和月份有关系,那么就从当前月的月份和天数来解决此问题,即以下解决方案

      • var currentDay; //当前月份天数
        var currentMonthDays = new Date(year, month, 0).getDate(); //拿到当前月份的天数
        var nextMonthDays = new Date(year, month + 1, 0).getDate();  //拿到下一月份的天数
        var days = currentDay > nextMonthDays? nextMonthDays: currentDay; //进行比较,如果当前月份天数大于下一月份天数,则days等于下一个月份的天数,否则为当前月份天数
        new Date(that.year, that.month, days);
        //即可解决此问题
        

我是书生,前端浪花中的一朵小浪花。