今天在朋友圈偶然看见一个好友自建了个网站记录自己的爱情,怀着好奇和祝福点进去围观了一下,没想到第二眼竟然发现了个bug,样子如下图
作为一个有强迫症的入门级前端工程师,直觉告诉我这个问题大概是数据类型转换造成的,于是用电脑打开链接准备扒拉一下网页源码一探究竟。但是用Chrome浏览器打开之后发现计时功能一切正常。再用safari打开,和手机上看到效果一样,至此可以确定这个问题必然是Date对象相关处理在Chrome和safari上存在兼容问题。
一、问题解决
上述网站计时功能通过每隔一秒取一次当前时间,然后减去他们爱情开始的时间2014-5-20(这个不是真实时间,仅作为祝福和说明问题),来计算一共在一起的时间。通过对这段代码进行学习,发现问题出在创建Date实例过程。
const start = new Date("2014-5-20");
通过new关键字创建Date实例有三种方式:
1、不提供参数,则新创建的Date实例代表当前时间;2、提供整型参数,参数表示自1970年1月1日0时0分0秒以来的毫秒数,新创建的Date实例表示经过了参数参数所代表的毫秒数之后的时间;
3、提供时间戳字符串,新创建的Date实例表示字符串所代表的时间
前述产生bug的代码通过传入时间戳字符串来创建Date实例,对于传入的字符串格式有严格规范限制,同时不同浏览器处理方式存在差异,因此造成了safari和Chrome不兼容的情况。只需要将上述创建实例的代码修改为如下即可。
const start = new Date("2014-05-20");
总体而言,Chrome中对时间戳字符串兼容情况优于safari,MDN规范中不推荐使用这种方式创建Date实例。如果要使用这种方式,建议严格遵守时间戳字符串格式规范。
根据ecma-262规范,时间戳字符串格式基于rfc2822或ISO 8601规范的确定。根据rfc2822规范时间戳字符串可定义为day-of-week date time zone;ISO 8601规范时间戳字符串格式定义为YYYY-MM-DDTHH:mm:ss.sssZ。
rfc2822规范中,day-of-week取值"Mon" / "Tue" / "Wed" / "Thu" /"Fri" / "Sat" / "Sun"。date表示日期,默认顺序为月日年。月取值为 "Jan" / "Feb" / "Mar" / "Apr" /"May" / "Jun" / "Jul" / "Aug" /"Sep" / "Oct" / "Nov" / "Dec"。time表示“时分秒”格式时间,均为两位数,zone表示时区。
ISO 8601规范中YYYY-MM-DD表示年月日,T为分隔符,表示后面为时间,HH:mm:ss.sss表示时、分、秒、毫秒,Z表示时区。(关于规范这一块内容比较多,我还没完全搞清楚)
二、知识拓展
Date类比较复杂,首先Date可以直接作为函数调用,又可以作为构造函数进行实例化,实例化的过程还可以传入多种类型参数。第二Date即包含静态方法,也包含大量实例方法,并且这些实例方法的返回结果有些地方违反常识且标准不统一,同时还存在静态方法和实例方法功能重合,实例方法间存在功能重合的问题。第三Date实例表示的时间状态是可变的,可以通过实例的相关set方法,改变Date对象所表示的时间状态。
1、Date
如果将Date当作函数直接调用,则返回当前时间戳字符串,new Date()实例化的结果则代表当前时间对象。在此不做过多讨论。当Date作为构造函数进行调用时,存在三种情况,在本文第一节已经讲过。
2、相关操作
Date的相关操作大致可以分为三类:得到Date实例、从实例中获取时间相关数据、时间操作。
得到Date实例很简单,如果需要得到当前时间直接通过实例化操作即可,如果需要得到指定时间的实例对象,可在实例化Date时传入时间参数。常见语法有:
new Date(intValue)
对于实例化过程传入整型参数,需要注意一下,Date可接受的整型参数范围并不是所有整数的范围,而是从1970年1月1日算起,向前或向后数100,000,000(一亿)天对应的毫秒范围,即可传入的整数区间为[-24*3600*1000*一亿,24*3600*1000*一亿]。
从实例中获取时间相关数据就是获取年、月、日、时、分、秒、毫秒等信息。获取年份信息很简单,直接调用Date对象的getFullYear()方法即可获得。虽然还存在getYear()方法,但是规范已经不推荐使用。时、分、秒、毫秒获取也很简单,以时获取为例,直接通过getHours()即可获取,范围为0~23的整数,分、秒、毫秒获取方式类似,而且返回值取值范围逻辑也是类似。
比较需要注意的是月、日信息的获取。月分信息通过getMonth()获取,返回值为0~11的整数,对应1到12月。到这里方法逻辑还算正常,但是不正常的事情马上要到了。日在这里有两层含义,一个是几月几日,一个是星期几,学到这里我也算终于明白了才开始学英语时为啥总绕不清what is the day today?和what is the date today?的关系,只能说汉语的博大精深,一个日包含太多意思。言归正传,在js中,几月几日的和星期几的获取还是遵循英语语义规范的,getDate()获取几月几日,getDay()获取星期几。至此你以为坑结束了吗?完全没有,getDate()的返回值是1~31,有木有,太不程序员思维了竟然不从0开始计数。而getDay()的返回值是0~6,可能一般人会庆幸一下终于正常了,然鹅,好戏才刚刚开始,这里0表示星期日😂,1~6分别表示星期一到星期六。一般来说,掌握了getMonth()、getDay()和getDate()的坑,可以处理绝大多数情况。
最后一部分是Date对象操作,前面已经提到,时间对象是Mutable的,比如你取了当前时间对象,但是可以通过set操作改变该对象对应的实际时间,这里是直接对当前对象作出修改,并不是返回一个新对象,在时间应用这种情况下,这种操作逻辑确实很违反常识...
通过set进行时间对象操作除了对年月日时分秒毫秒的操作,还有一个setTime()方法,该方法接受一个整型参数,操作的结果和new Date(intValue)的结果一样。其他set年月日时分秒毫秒的操作只能算是都只是针对时间对象的局部进行操作,在这里有一点需要注意,如果set的值超出了实际范围,则会产生「进位」的效果。具体情况如下:
const now = new Date();//现在是2020年5月6日03:14:52
也正是由于set方法存在「进位」的效果,我们可以轻松获取到给定时间节点加几天或者减几天的日期。
但是不可否认Date类存在一些坑,因此在学习过Date的基础上,实际应用中,可考虑使用相关库来提升出错概率,常见如Moment.js、js-joda都值得推荐。
最后,再次送上我对文章开头提到的哥们儿的祝福!🎉~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。