在一些对当前时间的准确性要求比较高的场景,前端项目并不能完全信任设备的本地时间,此时就需要一些手段进行对钟。一般而言,本地时间的偏离可以认为是一个仅有offset不同,而计时器正常运作的偏离(即本地时间的流逝速度没有问题,只是当前时间有平移偏离)。在这种情况下,只要获取到本地时间与实际时间相差的时间戳即可进行对钟。本文不讨论具体获得该差值的方法,只讨论在已知差值的情况下如何在项目其他部分无感知的情况下应用该差值。
js内获取当前时间的方法都与Date类有关:
new Date() // 当不传入参数时,返回当前时间的Date对象
Date() // 不接受参数,返回标识当前时间的字符串
Date.now() // 不接受参数,返回当前时间的时间戳
我们自然会希望引入时间校准之后项目的其他地方是无感的,可以按平时的代码习惯来继续开发;这里我们使用的方案则是篡改原生的window.Date对象:
const OldDate = window.Date
cosnt NewDate = // .....
window.Date = NewDate
我们用自己编写的NewDate去覆盖原先的Date对象,而要完成我们的目的,我们就需要保证两点:
- 在调用上述三个可获取本地时间的函数时,令其相对本地时间偏移我们已知的时间偏差
- 对其他任意Date相关的方法,其表现应当与原生Date一致
我们先从上述三个方法中最简单的一个Date.now开始:
Date.now = () => {
return Date.now() + offset
}
重写Date.now很轻松,但是。。。好像也完全不适用于另外两个方法以及前面说的替换Date的方法。但是无论如何,至少我们知道Date.now很好搞定,大不了我们给后面的方案安上一个now的方法就是了。
接下来是比较麻烦的new Date(),显然这是一个构造函数,它也不能像now那样被在原来的对象上直接改写。考虑到我们希望新的NewDate的大部分方法和原先完全一致,显然很容易想到的就是继承:
class NewDate extends Date {
// offset参数,决定时间偏移量
static offset = 0
// 覆盖静态方法now
static now() {
return super.now() + NewDate.offset
}
// 新的构造函数
constructor(...params) {
// 仅在不传入参数的情况下取当前时间,因此我们也只处理不传入参数的情况
if(params.length === 0) {
super(NewDate.now())
}
super(...params)
}
}
这样Date.now和new Date就都搞定了,但是这样的做法有两个问题,一个是class是es6再加的,另一个就是class是不支持你使用NewDate()这样的形式调用的。那么我们就需要把class的写法退化到更老的兼容写法,js中的类实际上是一个函数,而实现函数之后使用Object.setPrototypeOf修改原型链即可实现继承。
const OldDate = window.Date
const NewDate = function () {
// this instanceof NewDate 用于区分是否使用new来调用
if (this instanceof NewDate) {
return arguments.length === 0 ? new OldDate(OldDate.now() + NewDate.offset) : new window.Date(...arguments);
}
return new OldDate(OldDate.now() + NewDate.offset).toString()
};
NewDate.now = () => {
return OldDate.now() + NewDate.offset
}
Object.setPrototypeOf(NewDate, OldDate)
NewDate.offset = 0;
window.Date = NewDate
完成后设置Date.offset之后,Date相关的方法就会带上时间的偏移值