之前我在文章中说自己写时间处理库,有网友问:为什么要自己造一个轮子?是不知道市面上有成熟的库吗?有什么优势吗?
其实我没想那么多,就是把"造一个自己的库"当作一个"课题"或者"挑战"。想要做一个自己用着趁手的工具,并不是别人的不好用,而是想要一个符合自己使用习惯的工具。市面上的库无论方法命名还是使用的逻辑,并不一定符合自己的直觉,而且在开发过程,也能梳理自己的思路。
言归正传,谈谈我对这个库的规划。首先它需要有以下常见功能:
- 不同时间数据格式的识别:时间字符串、数组、对象、Date对象、时间戳、ZonedDateTime对象等等;
- 对时间进行加减单位、大小对比、不同单位取值,方法支持链式调用;
- 不同时间数据格式输出;
- 时区和语言全局切换和实例单独切换;
- 各种功能之间隔离管理,并且具有扩展性,用户可以自行扩展功能;
调用方式实现
期待的使用方式是像jQuery一样可以实现无new实例化和链式调用
javascript
datex(2022,10,1).change('year',1).format()
实现方式我直接仿照jQuery的写法
javascript
import prototype from './prototype.js';
// 构造器
function datex(...argu){
return new datex.prototype.init(...argu);
}
// 无new实例化 - 类似于jQuery
datex.prototype = prototype;
prototype.init.prototype = prototype;
原型设计
实例的方法全部继承于原型,原型的设计直接决定datex().xxx后的方法
javascript
export default {
_date:null,
parse:function(...argu){
// 解析代码
this._date = xxx;
return this;
},
init:function(...argu){
this.parse(...argu);
// 初始化
let _ = this;
taskQueue.forEach(function(task){
task.bind(_)(...argu);
});
return this;
},
onInit(callback){
taskQueue.push(callback);
}
};
数据解析
parse用于解析不同数据格式输入,解析后转换成标准的date对象,_date属性记录每一个实例对象的当前时间。可以把它当作一个时间的"信标",后续所有计算本质是对这个date对象的处理。这也是为什么我将这个库取名为"datex"的原因,我是对它进行扩展重构。
支持链式调用的实例方法都返回当前实例对象this,让方法可以延续;
实例初始化
init为原型初始化方法,taskQueue是内部预留的初始化事件处理队列,用于不同扩展修改初始化方法时使用。后续扩展如何想要在初始化时,扩展解析含蓄,可以在onInit中加入处理逻辑
javascript
proto.onInit(function(...argu){
// 重写方法,可以对实例进行补充
});
目录设计
text
/module - 功能模块
/method
/config - 配置
period.js - 时间间隔配置
/data - 数据
timezone - 时区列表
/locale - 语言包
en-US.json - 英语
zh-CN.json - 简体中文
zh-HK.json - 繁体中文
/runtime - 运行环境兼容
globalThis.js
structuredClone.js
/utils - 工具方法
type.js - 类型判断
base.js - 基础方法
compare.js - 时间对比方法
compute.js - 时间计算方法
duration.js - 时间间隔
language.js - 多语言处理
parse.js - 解析器扩展
temporal.js - 对Temporal的兼容处理
timezone.js - 时区支持
factory.js - 工厂函数
prototype.js - 原型对象
datex.js - 入口文件
功能模块设计
模块加载
功能模块不是直接写在原型里的,而是按功能模块次序加载。其中datex.extend是原型暴露的扩展方法,可以用于第三方扩展使用,这里内部功能引用也通过它扩展。
javascript
import datex from './module/factory.js';
import baseLoader from './module/method/base.js';
import computeLoader from './module/method/compute.js';
import compareLoader from './module/method/compare.js';
import durationLoader from './module/method/duration.js';
import languageLoader from './module/method/language.js';
import timezoneLoader from './module/method/timezone.js';
import temporalLoader from './module/method/temporal.js';
import parseLoader from './module/method/parse.js';
// 功能加载
[
baseLoader,
computeLoader,
compareLoader,
durationLoader,
languageLoader,
timezoneLoader,
temporalLoader,
parseLoader,
].forEach(datex.extend);
export default datex;
模块设计
模块仅暴露一个唯一方法function(datex,proto){},就是一个加载器。
datex就是构造器,对它进行方法扩展就是静态方法的实现。如datex.xxx这样的调用。
proto就是原型,对它进行方法扩展就是实例方法的实现。如datex().xxx这样的调用。
proto.onInit是在实例化阶段,对初始化方法重写处理。
javascript
export default function(datex,proto){
Object.assign(datex,{
getTimezoneOffset(){
}
});
Object.assign(proto,{
_timezone:null,
_offset:0,
getTimezoneOffset(){
}
});
proto.onInit(function(...argu){
});
// 重写
let set = proto.set;
let toObject = proto.toObject;
Object.assign(proto,{
toObject(){
let that = this.clone();
const temp = toObject.bind(that)();
return temp;
},
set(unit,value){
}
});
};
方法重写
对象可以通过Object.assign进行扩展或者覆盖,但是扩展并不是将原有的方法直接放弃掉,我们需要先另名保存一份,在新方法最前面调用即可,如上面的const temp = toObject.bind(that)();就是将原有方法绑定进新环境内。
总结
以上说的都是时间处理库实现的大体架构,具体实现其实就是对datex和proto两个对象进行方法的编写。核心是对实例中的_date进行计算。
【时间处理库仓库】 github.com/mumuy/datex...
【时间处理库文档】 passer-by.com/datex/
基于本项目实现的世界时间网站
【几点了】 jidianle.com/
【几点钟】 jidianzhong.com/
