前言
阅读源码的分支是 master
源码传送门:github.com/vercel/ms
ms 介绍
ms 是用于将各种时间格式转化为毫秒
ms('2 days') // 172800000
ms('1d') // 86400000
ms('10h') // 36000000
功能
通过单元测试和 readme.md 可以大概了解这个包的功能
// src/index.test.ts
import ms from './index';
describe('ms(string)', () => {
it('should not throw an error', () => {
expect(() => {
ms('1m');
}).not.toThrowError();
});
// ...
});
// long strings
// numbers
可以得出 ms 方法以下信息
- 接收
string和number类型的输入 long为默认值false的情况下输入带时间单位的字符串会转换为毫秒数值long为默认值false的情况下输入数字会转换为对应的带缩写单位的字符串long为true的情况下输入数字会转换为对应的正常单位的字符串
源代码
在
package.json中找到入口文件是index.ts,在主入口文件中对主函数(msFn)进行调用,使用auto-attach进行调试。
// src/index.ts
function msFn(value: StringValue, options?: Options): number;
function msFn(value: number, options?: Options): string;
function msFn(value: StringValue | number, options?: Options): number | string {
try {
if (typeof value === 'string' && value.length > 0) {
return parse(value);
} else if (typeof value === 'number' && isFinite(value)) {
return options?.long ? fmtLong(value) : fmtShort(value);
}
throw new Error('Value is not a string or number.');
} catch (error) {
const message = isError(error)
? `${error.message}. value=${JSON.stringify(value)}`
: 'An unknown error has occurred.';
throw new Error(message);
}
}
export default msFn;
使用 typescript 的函数重载,第一个函数声明接受两个参数:必填参数 value 和可选参数 options;
value 的类型是 StringValue,是 ms 自定义的类型。
// ...
const y = d * 365.25;
type Unit =
| 'Years'
| 'Year'
| 'Yrs'
| 'Yr'
| 'Y'
// ...
type UnitAnyCase = Unit | Uppercase<Unit> | Lowercase<Unit>;
export type StringValue =
| `${number}`
| `${number}${UnitAnyCase}`
| `${number} ${UnitAnyCase}`;
- 从以上联合类型可看出
value接收的值是'1'\'1Years'\'1 Years'等等 - 此处使用 儒略历;平年365日,4年1闰,历年的平均长度为365.25日
string 类型参数
匹配第一个判断,进入到
parse函数:
const match =
/^(?<value>-?(?:\d+)?\.?\d+) *(?<type>milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(
str,
);
const groups = match?.groups as { value: string; type?: string } | undefined;
if (!groups) {
return NaN;
}
const n = parseFloat(groups.value);
const type = (groups.type || 'ms').toLowerCase() as Lowercase<Unit>;
switch (type) {
case 'years':
case 'year':
case 'yrs':
case 'yr':
case 'y':
return n * y;
// ...
- 使用正则两个 命名捕获组,分别是
(?<value>x)和(?<type>x),用来匹配参数的 数值 和 单位 (?:\d+):是非捕获组,不能用来提取文本。正则引擎不需要记录捕获型括号匹配的内容,速度会更快,所用的内存也更少match?.groups as { value: string; type?: string } | undefined:typescript中命名捕获组需要手动指定
number 类型参数
匹配第二个判断
typeof value === 'number' && isFinite(value) 实现了 Number.isFinite 的 Polyfill。既避免单独使用全局的 isFinite 会强制将一个非数值的参数转换成数值,同时又增强了兼容性。
function plural(
ms: number,
msAbs: number,
n: number,
name: string,
): StringValue {
const isPlural = msAbs >= n * 1.5;
return `${Math.round(ms / n)} ${name}${isPlural ? 's' : ''}` as StringValue;
}
function fmtLong(ms: number): StringValue {
const msAbs = Math.abs(ms);
if (msAbs >= d) {
return plural(ms, msAbs, d, 'day');
}
if (msAbs >= h) {
return plural(ms, msAbs, h, 'hour');
}
if (msAbs >= m) {
return plural(ms, msAbs, m, 'minute');
}
if (msAbs >= s) {
return plural(ms, msAbs, s, 'second');
}
return `${ms} ms`;
}
- 取参数的绝对值,若参数大于等于本身的 1.5 倍时,时间格式单位后缀加上
s
错误类型守卫
function isError(value: unknown): value is Error {
return typeof value === 'object' && value !== null && 'message' in value;
}
请各位不吝赐教(逃