接上篇 —— dayjs 源码解析(二):Dayjs 类 —— 继续解析 dayjs
的源码。
从本篇开始,分三篇解析 dayjs
源码中插件功能的部分,也就是 src/plugin
目录下的文件。
目录如下:
- dayjs 源码解析(一):概念、locale、constant、utils
- dayjs 源码解析(二):Dayjs 类
- dayjs 源码解析(三):插件(上)
- dayjs 源码解析(四):插件(中)
- dayjs 源码解析(五):插件(下)
插件加载
在 src/index.js
写了加载插件的方法 dayjs.extend
,很显然插件是一个函数,接收三个参数 option
、 Dayjs 类
和 dayjs 函数对象
。
并给 plugin 函数对象
设了个 $i
来保证单例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
dayjs.extend = (plugin, option) => { if (!plugin.$i) { plugin(option, Dayjs, dayjs); plugin.$i = true; } return dayjs; };
|
下面是大部分插件的标准写法,可以新加方法也可以覆盖原有的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
export default (o, c, d) => { const proto = c.prototype;
proto.demo = function (args) {}; };
|
is 系列
首选先从一些简单的插件开始解析。is
开头的插件都是用来判断的,返回一个 Boolean
值来表示是否。
isLeapYear
判断是否为闰年比较简单,必须同时满足以下两个条件:
- 能被
4
整除且不能被 100
整除;
- 能被
400
整除;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
export default (o, c) => { const proto = c.prototype;
proto.isLeapYear = function () { return (this.$y % 4 === 0 && this.$y % 100 !== 0) || this.$y % 400 === 0; }; };
|
isMoment
官网上并没有给这个方法的文档,其实想着判断是否为 Moment
的实例就不合理。所以直接判断是否为 Dayjs
的实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
export default (o, c, f) => {
f.isMoment = function (input) { return f.isDayjs(input); }; };
|
isBetween、isSameOrAfter、isSameOrBefore
三个判断相对早晚的方法实现的原理基本一致。都是组合的 Dayjs.prototype.isSame
、Dayjs.prototype.isBefore
和 Dayjs.prototype.isAfter
,很简单。
特别提到的是 isBetween
,用的是数学上的 ()
和 []
来表示两个的开闭性的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
|
export default (o, c) => {
c.prototype.isSameOrAfter = function (that, units) { return this.isSame(that, units) || this.isAfter(that, units); }; };
export default (o, c) => {
c.prototype.isSameOrBefore = function (that, units) { return this.isSame(that, units) || this.isBefore(that, units); }; };
export default (o, c, d) => {
c.prototype.isBetween = function (a, b, u, i) { const dA = d(a); const dB = d(b); i = i || '()'; const dAi = i[0] === '('; const dBi = i[1] === ')';
return ( ((dAi ? this.isAfter(dA, u) : !this.isBefore(dA, u)) && (dBi ? this.isBefore(dB, u) : !this.isAfter(dB, u))) || ((dAi ? this.isBefore(dA, u) : !this.isAfter(dA, u)) && (dBi ? this.isAfter(dB, u) : !this.isBefore(dB, u))) ); }; };
|
isToday、isTomorrow、isYesterday
这三个判断是否在昨天、今天和明天的方法实现的原理也是一样的。都是用当前实例
和被比较日的实例
,同时格式化成 YYYY-MM-DD
的形式,然后比较字符串是否相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
|
export default (o, c, d) => { const proto = c.prototype;
proto.isToday = function () { const comparisonTemplate = 'YYYY-MM-DD'; const now = d();
return this.format(comparisonTemplate) === now.format(comparisonTemplate); }; };
export default (o, c, d) => { const proto = c.prototype;
proto.isTomorrow = function () { const comparisonTemplate = 'YYYY-MM-DD'; const tomorrow = d().add(1, 'day');
return ( this.format(comparisonTemplate) === tomorrow.format(comparisonTemplate) ); }; };
export default (o, c, d) => { const proto = c.prototype;
proto.isYesterday = function () { const comparisonTemplate = 'YYYY-MM-DD'; const yesterday = d().subtract(1, 'day');
return ( this.format(comparisonTemplate) === yesterday.format(comparisonTemplate) ); }; };
|
week 系列
分析 week
系列的代码前,先来普及下关于“周(week
)”这个单位的一些基本知识。
ISO8601 对周做了规定:本年度第一个周四
所在的周为本年度的第 1
周。这句话与下面的三个说法等价:
1 月 4 日
所在的周四;
- 本年度第一个至少有
4
天在同一周内的周;
- 周一在去年
12 月 29 日
至今年 1 月 4 日
以内的周;
这种定义方式就会产生一个问题,每年的靠近 1 月 1 日的前后几天,在 ISO8601 的周历算法中,可能并不属于所在的那一年。
举个例子,2021 年 1 月 1 日
是周五,2021 年
第一个周四是 1 月 7 日
,所以在 ISO 周历算法中, 1 月 4 日
到 1 月 10 日
这一周才是 2021
年的第 1 周
;也就代表着 12 月 28 日
到 1 月 3 日
这一周是 2020
年的第 53 周
。
ISO8601 还把一周第一天
定义为了周一
。通过初始周
和周第一天
的定义,把 1
年分为了 52
周或 53
周。
这种对周的算法主要应用于政府和商务的会计年度。
ISO week
在 src/plugin
中关于 iso
的插件有两个:isoWeek
和 isoWeeksInYear
。
isoWeek
isoWeek
插件是一个比较大的插件,它在 Dayjs.prototype
上添加和拓展了四个方法:
isoWeekYear
: 获取实例所在的 ISO 周
所在的年;
isoWeek
: 获取或设置年度的第 ISO 周数
;
isoWeekday
: 获取或设置一周的第 ISO 日
,范围是 1-7
;
startOf
: 扩展 .startOf
.endOf
的 APIs
,使其支持单位 isoWeek
;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
| import { D, W, Y } from '../../constant';
const isoWeekPrettyUnit = 'isoweek';
export default (o, c, d) => {
const getYearFirstThursday = (year, isUtc) => { const yearFirstDay = (isUtc ? d.utc : d)().year(year).startOf(Y); let addDiffDays = 4 - yearFirstDay.isoWeekday(); if (yearFirstDay.isoWeekday() > 4) { addDiffDays += 7; } return yearFirstDay.add(addDiffDays, D); };
const getCurrentWeekThursday = (ins) => ins.add(4 - ins.isoWeekday(), D);
const proto = c.prototype;
proto.isoWeekYear = function () { const nowWeekThursday = getCurrentWeekThursday(this); return nowWeekThursday.year(); };
proto.isoWeek = function (week) { if (!this.$utils().u(week)) { return this.add((week - this.isoWeek()) * 7, D); } const nowWeekThursday = getCurrentWeekThursday(this); const diffWeekThursday = getYearFirstThursday(this.isoWeekYear(), this.$u); return nowWeekThursday.diff(diffWeekThursday, W) + 1; };
proto.isoWeekday = function (week) { if (!this.$utils().u(week)) { return this.day(this.day() % 7 ? week : week - 7); } return this.day() || 7; };
const oldStartOf = proto.startOf;
proto.startOf = function (units, startOf) { const utils = this.$utils(); const isStartOf = !utils.u(startOf) ? startOf : true; const unit = utils.p(units); if (unit === isoWeekPrettyUnit) { return isStartOf ? this.date(this.date() - (this.isoWeekday() - 1)).startOf('day') : this.date(this.date() - 1 - (this.isoWeekday() - 1) + 7).endOf('day'); } return oldStartOf.bind(this)(units, startOf); }; };
|
isoWeeksInYear
这个方法就比较简单,计算实例所在年的 ISO 周
总数,52
或 53
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
export default (o, c) => { const proto = c.prototype; proto.isoWeeksInYear = function () { const isLeapYear = this.isLeapYear(); const last = this.endOf('y'); const day = last.day(); if (day === 4 || (isLeapYear && day === 5)) { return 53; } return 52; }; };
|
普通 week
ISO8601
统一了对于周的历法,但是在各个国家和地区的传统中,对于周历
中,每年的初始周
和每周的初始日
定义都各不相同。在 dayjs
中,把这两种设置在了 locale
中。
以汉语举例:
1 2 3 4 5 6
| const locale = { weekStart: 1, yearStart: 4, };
|
可以发现,汉语环境下周历法的设置与 ISO8601
周历法一致。普通的 week
插件是如下三个:
weekday
:在当前语言环境下,获取或设置实例是本周的第几天;
weekOfYear
:在当前语言环境下,获取或设置实例是年中第几周;
weekYear
:在当前语言环境下,获取的按周历算,实例所在的年份;
weekday
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
export default (o, c) => { const proto = c.prototype;
proto.weekday = function (input) { const weekStart = this.$locale().weekStart || 0; const { $W } = this; const weekday = ($W < weekStart ? $W + 7 : $W) - weekStart; if (this.$utils().u(input)) { return weekday; } return this.subtract(weekday, 'day').add(input, 'day'); }; };
|
weekOfYear
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| import { MS, Y, D, W } from '../../constant';
export default (o, c, d) => { const proto = c.prototype;
proto.week = function (week = null) { if (week !== null) { return this.add((week - this.week()) * 7, D); } const yearStart = this.$locale().yearStart || 1; if (this.month() === 11 && this.date() > 25) { const nextYearStartDay = d(this).startOf(Y).add(1, Y).date(yearStart); const thisEndOfWeek = d(this).endOf(W); if (nextYearStartDay.isBefore(thisEndOfWeek)) { return 1; } } const yearStartDay = d(this).startOf(Y).date(yearStart); const yearStartWeek = yearStartDay.startOf(W).subtract(1, MS); const diffInWeek = this.diff(yearStartWeek, W, true); if (diffInWeek < 0) { return d(this).startOf('week').week(); } return Math.ceil(diffInWeek); };
proto.weeks = function (week = null) { return this.week(week); }; };
|
weekYear
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
export default (o, c) => { const proto = c.prototype;
proto.weekYear = function () { const month = this.month(); const weekOfYear = this.week(); const year = this.year(); if (weekOfYear === 1 && month === 11) { return year + 1; } return year; }; };
|
下篇继续分析插件。
前端记事本,不定期更新,欢迎关注!