dayjs 分享
2018-09-12 本文已影响3人
小雨小雨丶
<p style='text-align: center; color: #599FF0;'>Dayjs </p>
分享内容
- 框架结构
- 涉及技术
- 部分代码解读
- 总结
概述
Day.js 是一个轻量的处理时间和日期的 JavaScript 库,和 Moment.js 的 API 设计保持完全一样. 如果你曾经用过 Moment.js, 那么你已经知道如何使用 Day.js
优点
🕒 和Moment.js有着相同的API和模式
💪 不可变、持久性
🔥 提供链式调用
🌐 国际化标准
📦 超小的压缩体积,仅仅有2kb左右
👫 极大多数的浏览器兼容
目录:
build/ -> 构建工具
index.js -> 入口文件
rollup.config.js -> rollup配置文件
dosc/ -> 文档
en -> 英文
... -> 等等
src/ -> 源代码
locale/ -> 各种语言
zh-cn.js -> 中文
... -> 等等
plugin/ -> 内部提供插件
advancedFormat/ -> 特色格式化
index.js -> index.js
buddhistEra/ -> 支持农历
index.js -> ..
isLeapYear/ -> 是否为闰年
index.js -> ..
relativeTime/ -> 相对时间方法集合,包括 to、from、toNow、fromNow
index.js -> ..
constant.js -> 默认常量,包括时间单位,正则
index.js -> 主文件
util.js -> 工具方法
test/ -> jest自动化测试工具
... -> 测试用例
.babelrc -> babel配置
.eslintrc.json -> eslint配置
.gitignore -> git忽略
.npmignore -> npm忽略
.travis.yml -> travis配置
CHANGELOG.md -> 自动输出的更新日志
CONTRIBUTING.md -> 贡献文档
LICENSE -> 协议
README.md -> readme
index.d.ts -> ts静态检查声明文件
karma.saucs.conf.js -> karma配置文件
package.json -> 包配置
涉及技术:
名称 | 概述 |
---|---|
rollup | JavaScript 模块打包器,dayjs中仅仅用来打包,并没有用到太多的rollup特性,比如 Tree Shaking |
jest | JavaScript 测试框架 |
Karma | 测试过程管理工具,通常用于浏览器兼容 |
travis | 持续集成服务,与github绑定,仓库中只要有新的代码,就会自动抓取。然后,提供一个运行环境,执行测试,构建,并且部署到服务器。 |
semantic-release | 版本管理,自动化发布 |
typescript | javascript超集,用来提供类型检查 |
eslint | 代码检查 |
bebel | 代码编译 |
rollup配置:
// rollup.config.js
const babel = require('rollup-plugin-babel');
const uglify = require('rollup-plugin-uglify');
module.exports = config => {
const { input, fileName, name } = config;
return {
input: {
input,
external: ['dayjs'], // 外部文件,不进行打包
plugins: [
babel({
exclude: 'node_modules/**'
}),
uglify()
]
},
output: {
file: fileName,
format: 'umd', // 采用umd规范
name: name || 'dayjs',
// 重命名全局命名
globals: {
dayjs: 'dayjs'
}
}
};
};
// index.js核心代码
async function build(option) {
const bundle = await rollup.rollup(option.input);
await bundle.write(option.output);
}
(async () => {
try {
// 打包 locale
const locales = await promisifyReadDir(
path.join(__dirname, '../src/locale')
);
locales.forEach(l => {
build(
configFactory({
input: `./src/locale/${l}`,
fileName: `./locale/${l}`,
name: `dayjs_locale_${formatName(l)}`
})
);
});
// 打包plugins
const plugins = await promisifyReadDir(
path.join(__dirname, '../src/plugin')
);
plugins.forEach(l => {
build(
configFactory({
input: `./src/plugin/${l}`,
fileName: `./plugin/${l}`,
name: `dayjs_plugin_${formatName(l)}`
})
);
});
// 打包index
build(
configFactory({
input: './src/index.js',
fileName: './dayjs.min.js'
})
);
} catch (e) {
console.error(e); // eslint-disable-line no-console
}
})();
src部分
constant.js:内是一些英文语义变量的定义
utils.js:包含一些常用的工具方法。
padStart(string, length, pad): 补充前缀
padZoneStr(negMinuts): 转换时间格式为hh(hour):mm(minute) => 为了转换格林威治时间
monthDiff(a, b): 返回月份差,基于a
absFloor(n):忽略符号的Math.floor
prettyUnit(units): 统一化单位 units => unit
isUndefined(s):是否为undefined
//utils.js中的monthDiff,精度 <= 月
const monthDiff = (a, b) => {
// function from moment.js in order to keep the same result
// 代码优化来自moment.js,反而更难理解了。
// 月份差
const wholeMonthDiff = (b.year() - a.year()) * 12 + (b.month() - a.month());
// 同化年月
const anchor = a.clone().add(wholeMonthDiff, 'months');
// 比day的大小
const c = b - anchor < 0; // => b < anchor
// 锚点2
const anchor2 = a.clone().add(wholeMonthDiff + (c ? -1 : 1), 'months');
// 返回精度至day的时间差
return Number(
-(
wholeMonthDiff +
(b - anchor) / (c ? anchor - anchor2 : anchor2 - anchor)
)
);
};
index.js
export default dayjs
let L = 'en' // global locale
const Ls = {} // global loaded locale
const isDayjs = d => d instanceof Dayjs;
const dayjs = (date, c) => {
if (isDayjs(date)) {
return date.clone()
}
const cfg = c || {}
cfg.date = date
return new Dayjs(cfg) // eslint-disable-line no-use-before-define
}
// parseDate方法
const parseDate = (date) => {
let reg
if (date === null) return new Date(NaN) // => Invalid Date
if (Utils.isUndefined(date)) return new Date()
if (date instanceof Date) return date
if ((typeof date === 'string')
&& (/.*[^Z]$/i.test(date)) // Z代表格林威治时间和本地时间之间的时差
&& (reg = date.match(C.REGEX_PARSE))) { // ^(\d{4})-?(\d{1,2})-?(\d{0,2})(.*?(\d{1,2}):(\d{1,2}):(\d{1,2}))?.?(\d{1,3})?$/ 见下图
// 结果结构 => ["整体", "括号1的匹配", "括号2的匹配", "括号3的匹配", .....]
return new Date(
reg[1], reg[2] - 1, reg[3] || 1,
reg[5] || 0, reg[6] || 0, reg[7] || 0, reg[8] || 0
)
}
return new Date(date) // timestamp
}
// parseLocale方法
const parseLocale = (preset, object, isLocal) => {
let l
if (!preset) return null
if (typeof preset === 'string') {
if (Ls[preset]) {
l = preset
}
if (object) {
Ls[preset] = object
l = preset
}
} else {
const { name } = preset
Ls[name] = preset
l = name
}
if (!isLocal) L = l
return l
}
[图片上传失败...(image-e077f8-1531296814278)]
// Dayjs类
class Dayjs {
constructor(cfg) {
this.parse(cfg)
}
parse(cfg) {
this.$d = parseDate(cfg.date)
this.init(cfg)
}
init(cfg) {
this.$y = this.$d.getFullYear()
this.$M = this.$d.getMonth()
this.$D = this.$d.getDate()
this.$W = this.$d.getDay()
this.$H = this.$d.getHours()
this.$m = this.$d.getMinutes()
this.$s = this.$d.getSeconds()
this.$ms = this.$d.getMilliseconds()
this.$L = this.$L || parseLocale(cfg.locale, null, true) || L
}
$utils() {}
isValid() {}
isLeapYear() {}
$compare(that) {}
isSame(that) {}
isBefore(that) {}
isAfter(that) {}
year() {}
month() {}
day() {}
date() {}
hour() {}
minute() {}
second()
millisecond() {}
unix() {}
valueOf() {}
daysInMonth() {}
$locale() {}
locale(preset, object) {}
clone() {}
toDate() {}
toArray() {}
toJSON() {}
toISOString() {}
toObject() {}
toString() {}
startOf(units, startOf) {}
endOf(arg) {}
$set(units, int) {}
set(string, int) {}
add(number, units) {}
subtract(number, string) {}
format(formatStr) {}
diff(input, units, float) {
const unit = Utils.prettyUnit(units)
const that = dayjs(input)
const diff = this - that
let result = Utils.monthDiff(this, that)
switch (unit) {
case C.Y:
result /= 12
break
case C.M:
break
case C.Q:
result /= 3
break
case C.W:
result = diff / C.MILLISECONDS_A_WEEK
break
case C.D:
result = diff / C.MILLISECONDS_A_DAY
break
case C.H:
result = diff / C.MILLISECONDS_A_HOUR
break
case C.MIN:
result = diff / C.MILLISECONDS_A_MINUTE
break
case C.S:
result = diff / C.MILLISECONDS_A_SECOND
break
default: // milliseconds
result = diff
}
return float ? result : Utils.absFloor(result)
}
}
总结
- 结构简洁,清晰,在不失可用性的前提下,尽可能的简化体积
- 代码抽离彻底,无重复,无强耦合
- 使用了自动化测试,和版本控制、发布,大大解放人力
在阅读过程中,学到了如何规划中小型框架结构,如何使用自动化工具,也掌握了关于时间的一些细节,比如格林威治时间。
当然,dayjs也不是完美的,也有一些地方可以简化、优化,比如 undefined 替换为 void 0 以节约字节(rollup会将undefined替换为void 0)