最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • dayjs源码解析(三):插件(上)

    正文概述 掘金(林景宜)   2020-12-18   561

    接上篇 —— dayjs 源码解析(二):Dayjs 类 —— 继续解析 dayjs 的源码。

    从本篇开始,分三篇解析 dayjs 源码中插件功能的部分,也就是 src/plugin 目录下的文件。

    目录如下:

    1. dayjs 源码解析(一):概念、locale、constant、utils
    2. dayjs 源码解析(二):Dayjs 类
    3. dayjs 源码解析(三):插件(上)
    4. dayjs 源码解析(四):插件(中)
    5. dayjs 源码解析(五):插件(下)

    插件加载

    src/index.js 写了加载插件的方法 dayjs.extend,很显然插件是一个函数,接收三个参数 optionDayjs 类dayjs 函数对象

    并给 plugin 函数对象设了个 $i 来保证单例。

    /**
     * @description: 挂载插件
     * @param {Function} plugin 插件
     * @param {*} option 插件选项
     * @return {dayjs function} 返回 dayjs 函数对象
     */
    dayjs.extend = (plugin, option) => {
      // 同一个插件只挂载一次
      if (!plugin.$i) {
        plugin(option, Dayjs, dayjs); //挂载
        plugin.$i = true;
      }
      return dayjs;
    };
    

    下面是大部分插件的标准写法,可以新加方法也可以覆盖原有的方法:

    /**
     * @description: plugin
     * @param {Object} o option
     * @param {Class} c Dayjs类
     * @param {Function} d dayjs函数对象
     */
    export default (o, c, d) => {
      const proto = c.prototype;
      /**
       * @description: demo
       * @return {Boolean}
       */
      proto.demo = function (args) {};
    };
    

    is 系列

    首选先从一些简单的插件开始解析。is 开头的插件都是用来判断的,返回一个 Boolean 值来表示是否。

    isLeapYear

    判断是否为闰年比较简单,必须同时满足以下两个条件:

    1. 能被 4 整除且不能被 100 整除;
    2. 能被 400 整除;
    /**
     * @description: plugin
     * @param {Object} o option
     * @param {Class} c Dayjs类
     */
    export default (o, c) => {
      const proto = c.prototype;
      /**
       * @description: 返回一个 boolean 来展示一个 Day.js 对象的年份是不是闰年。
       * @return {Boolean}
       */
      proto.isLeapYear = function () {
        // 判断闰年需要满足的两种情况,1.能被4整除且不能被100整除 2.能被400整除
        return (this.$y % 4 === 0 && this.$y % 100 !== 0) || this.$y % 400 === 0;
      };
    };
    

    isMoment

    官网上并没有给这个方法的文档,其实想着判断是否为 Moment 的实例就不合理。所以直接判断是否为 Dayjs 的实例。

    /**
     * @description: plugin
     * @param {Object} o option
     * @param {Class} c Dayjs类
     * @param {Function} f dayjs函数对象
     */
    export default (o, c, f) => {
      /**
       * @description: 指示对象是不是Dayjs的实例
       * @param {Object} input
       * @return {Boolean}
       */
      f.isMoment = function (input) {
        // 最终用的是判断是不是Dayjs的实例?
        return f.isDayjs(input);
      };
    };
    

    isBetween、isSameOrAfter、isSameOrBefore

    三个判断相对早晚的方法实现的原理基本一致。都是组合的 Dayjs.prototype.isSameDayjs.prototype.isBeforeDayjs.prototype.isAfter,很简单。

    /**
     * @description: plugin
     * @param {Object} o option
     * @param {Class} c Dayjs类
     */
    export default (o, c) => {
      /**
       * @description: 返回一个 boolean 来实例是否和一个时间相同或在该时间之前。
       * @param {Dayjs} that 另一个Dayjs实例
       * @param {String} unit 时间单位
       * @return {Boolean}
       */
      c.prototype.isSameOrAfter = function (that, units) {
        // 调用了 isSame 和 isAfter
        return this.isSame(that, units) || this.isAfter(that, units);
      };
    };
    /**
     * @description: plugin
     * @param {Object} o option
     * @param {Class} c Dayjs类
     */
    export default (o, c) => {
      /**
       * @description: 返回一个 boolean 来实例是否和一个时间相同或在该时间之后。
       * @param {Dayjs} that 另一个Dayjs实例
       * @param {String} unit 时间单位
       * @return {Boolean}
       */
      c.prototype.isSameOrBefore = function (that, units) {
        // 调用了 isSame 和 isBefore
        return this.isSame(that, units) || this.isBefore(that, units);
      };
    };
    /**
     * @description: plugin
     * @param {Object} o option
     * @param {Class} c Dayjs类
     * @param {Function} d dayjs函数对象
     */
    export default (o, c, d) => {
      /**
       * @description: 返回一个 boolean 来展示一个时间是否介于两个时间之间
       * @param {String|Dayjs} a 时间
       * @param {String|Dayjs} b 时间
       * @param {String} u unit 时间单位
       * @param {String} i include 区间开闭性 () (] [] [)
       * @return {Boolean} 指示时间是否介于两个时间之间
       */
      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] === ')';
    
        // 利用原型上的 isAfter 和 isBefore 实现 isBetween 判断
        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 的形式,然后比较字符串是否相同。

    /**
     * @description: plugin
     * @param {Object} o option
     * @param {Class} c Dayjs类
     * @param {Function} d dayjs函数对象
     */
    export default (o, c, d) => {
      const proto = c.prototype;
      /**
       * @description: 判断当前 Day.js 实例是否是今天。
       * @return {Boolean}
       */
      proto.isToday = function () {
        const comparisonTemplate = 'YYYY-MM-DD';
        const now = d();
    
        // 要比较的两个实例同时输出为 YYYY-MM-DD 格式字符串,相同就代表为同一天
        return this.format(comparisonTemplate) === now.format(comparisonTemplate);
      };
    };
    /**
     * @description: plugin
     * @param {Object} o option
     * @param {Class} c Dayjs类
     * @param {Function} d dayjs函数对象
     */
    export default (o, c, d) => {
      const proto = c.prototype;
      /**
       * @description: 判断当前 Day.js 对象是否是明天。
       * @return {Boolean}
       */
      proto.isTomorrow = function () {
        const comparisonTemplate = 'YYYY-MM-DD';
        // 新建一个明天的实例
        const tomorrow = d().add(1, 'day');
    
        // 要比较的两个实例同时输出为 YYYY-MM-DD 格式字符串,相同就代表为同一天
        return (
          this.format(comparisonTemplate) === tomorrow.format(comparisonTemplate)
        );
      };
    };
    /**
     * @description: plugin
     * @param {Object} o option
     * @param {Class} c Dayjs类
     * @param {Function} d dayjs函数对象
     */
    export default (o, c, d) => {
      const proto = c.prototype;
      /**
       * @description: 判断当前 Day.js 对象是否是昨天。
       * @return {Boolean}
       */
      proto.isYesterday = function () {
        const comparisonTemplate = 'YYYY-MM-DD';
        // 新建一个昨天的实例
        const yesterday = d().subtract(1, 'day');
    
        // 要比较的两个实例同时输出为 YYYY-MM-DD 格式字符串,相同就代表为同一天
        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 周

    dayjs源码解析(三):插件(上)

    ISO8601 还把一周第一天定义为了周一。通过初始周周第一天的定义,把 1 年分为了 52 周或 53 周。

    这种对周的算法主要应用于政府和商务的会计年度。

    ISO week

    src/plugin 中关于 iso 的插件有两个:isoWeekisoWeeksInYear

    isoWeek

    isoWeek 插件是一个比较大的插件,它在 Dayjs.prototype 上添加和拓展了四个方法:

    • isoWeekYear: 获取实例所在的 ISO 周所在的年;
    • isoWeek: 获取或设置年度的第 ISO 周数
    • isoWeekday: 获取或设置一周的第 ISO 日,范围是 1-7
    • startOf: 扩展 .startOf .endOfAPIs,使其支持单位 isoWeek
    import { D, W, Y } from '../../constant';
    
    const isoWeekPrettyUnit = 'isoweek';
    
    /**
     * @description: plugin ISO-8601 基于周的日历
     * @param {Object} o option
     * @param {Class} c Dayjs类
     * @param {Function} d dayjs函数对象
     */
    export default (o, c, d) => {
      /**
       * @description: 获取指定年的第一个星期四
       * @param {Number} year 年
       * @param {Boolean} isUtc 是否使用UTC模式
       * @return {Dayjs}
       */
      const getYearFirstThursday = (year, isUtc) => {
        const yearFirstDay = (isUtc ? d.utc : d)().year(year).startOf(Y);
        // 4 减 一月一号的星期几
        let addDiffDays = 4 - yearFirstDay.isoWeekday();
        if (yearFirstDay.isoWeekday() > 4) {
          addDiffDays += 7;
        }
        // 获得了指定年的第一个星期四的实例
        return yearFirstDay.add(addDiffDays, D);
      };
    
      /**
       * @description: 获取离实例日期最近的星期四
       * @param {Dayjs} ins 实例
       * @return {Dayjs} 返回新实例
       */
      // 4 减 今天的星期几
      const getCurrentWeekThursday = (ins) => ins.add(4 - ins.isoWeekday(), D);
    
      const proto = c.prototype;
    
      /**
       * @description: 获取实例所在的 ISO 周所在的年
       * @return {Number}
       */
      proto.isoWeekYear = function () {
        // 获取最近的星期四所在的年
        const nowWeekThursday = getCurrentWeekThursday(this);
        return nowWeekThursday.year();
      };
    
      /**
       * @description: 获取或设置年度的第 ISO 周数。
       * @param {Number} week ISO周数
       * @return {Number|Dayjs}
       */
      proto.isoWeek = function (week) {
        // setter 算出周差,再加上
        if (!this.$utils().u(week)) {
          return this.add((week - this.isoWeek()) * 7, D);
        }
        // getter
        // 最近周四的实例
        const nowWeekThursday = getCurrentWeekThursday(this);
        // 今年第一个周四的实例
        const diffWeekThursday = getYearFirstThursday(this.isoWeekYear(), this.$u);
        // 算出周差后加一就是周数
        return nowWeekThursday.diff(diffWeekThursday, W) + 1;
      };
    
      /**
       * @description: 获取或设置一周的第 ISO 日,范围是 1-7
       * @param {Number} week 有值则为setter,无值则为getter
       * @return {Number|Dayjs}
       */
      proto.isoWeekday = function (week) {
        // setter时,用this.day()除7取余
        if (!this.$utils().u(week)) {
          return this.day(this.day() % 7 ? week : week - 7);
        }
        // getter时,直接返回 this.day(), 0的时候就是7
        return this.day() || 7;
      };
    
      const oldStartOf = proto.startOf;
      /**
       * @description: 扩展 .startOf .endOf APIs 支持单位 isoWeek
       * @param {String} units 单位
       * @param {Boolean} startOf 标志,true:startOf, false: endOf
       * @return {Dayjs} 返回新的 Dayjs 实例,cfg与原实例相同
       */
      proto.startOf = function (units, startOf) {
        const utils = this.$utils();
        const isStartOf = !utils.u(startOf) ? startOf : true;
        // 处理下单位 isoWeek
        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');
        }
        // 普通情况还是用老版本的 oldStartOf 处理
        return oldStartOf.bind(this)(units, startOf);
      };
    };
    

    isoWeeksInYear

    这个方法就比较简单,计算实例所在年的 ISO 周总数,5253

    /**
     * @description: plugin
     * @param {Object} o option
     * @param {Class} c Dayjs类
     * @param {Function} d dayjs函数对象
     */
    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 中。

    以汉语举例:

    const locale = {
      // 可选,设置一周的开始,默认周日,1 代表周一
      weekStart: 1,
      // 可选,设置一年的开始周,包含1月4日的那一周作为第一周
      yearStart: 4,
    };
    

    可以发现,汉语环境下周历法的设置与 ISO8601 周历法一致。普通的 week 插件是如下三个:

    • weekday:在当前语言环境下,获取或设置实例是本周的第几天;
    • weekOfYear:在当前语言环境下,获取或设置实例是年中第几周;
    • weekYear:在当前语言环境下,获取的按周历算,实例所在的年份;

    weekday

    /**
     * @description: plugin
     * @param {Object} o option
     * @param {Class} c Dayjs类
     */
    export default (o, c) => {
      const proto = c.prototype;
      /**
       * @description: 获取或设置当前语言环境下本周的第几天。
       * @param {Number} input
       * @return {Number|Dayjs} getter时返回本周的第几天,setter时返回新实例
       */
      proto.weekday = function (input) {
        // locale 中设置的一周开始,中文是星期一开始,所以是weekStart = 1
        const weekStart = this.$locale().weekStart || 0;
        // 今天是周三,所以是$W = 3
        const { $W } = this;
        // 周三的 weekday = 3 - 1 = 2
        const weekday = ($W < weekStart ? $W + 7 : $W) - weekStart;
        if (this.$utils().u(input)) {
          return weekday;
        }
        // 减去2天,是周一,加上input天,就代表这设为了本周的第input天
        return this.subtract(weekday, 'day').add(input, 'day');
      };
    };
    

    weekOfYear

    import { MS, Y, D, W } from '../../constant';
    
    /**
     * @description: plugin
     * @param {Object} o option
     * @param {Class} c Dayjs类
     * @param {Function} d dayjs函数对象
     */
    export default (o, c, d) => {
      const proto = c.prototype;
      /**
       * @description: 在当前语言环境下,返回一个 number 来get实例是年中第几周,或者通过参数来set实例。默认的原型中是没有 week 这个单位的getter/setter的。
       * @param {Number} week set时的参数,设为第 week 周
       * @return {Number|setter} setter时返回新实例,getter时返回周数
       */
      proto.week = function (week = null) {
        // 有参数,也就是setter,此时就计算week差,然后add上
        if (week !== null) {
          return this.add((week - this.week()) * 7, D);
        }
        // getter
        // 获取设置的yearStart
        const yearStart = this.$locale().yearStart || 1;
        // 如果下一年的开始早于本周的最后,就代表是下一年的第一周,返回 1
        if (this.month() === 11 && this.date() > 25) {
          // d(this) is for badMutable
          const nextYearStartDay = d(this).startOf(Y).add(1, Y).date(yearStart);
          const thisEndOfWeek = d(this).endOf(W);
          if (nextYearStartDay.isBefore(thisEndOfWeek)) {
            return 1;
          }
        }
        // diffInWeek 小于 0,就返回本周第一天的week数
        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();
        }
        // 普通情况就直接返回 diffInWeek
        return Math.ceil(diffInWeek);
      };
    
      proto.weeks = function (week = null) {
        return this.week(week);
      };
    };
    

    weekYear

    /**
     * @description: plugin
     * @param {Object} o option
     * @param {Class} c Dayjs类
     */
    export default (o, c) => {
      const proto = c.prototype;
      /**
       * @description: 在当前语言环境下,获取的按周历算,实例所在的年份。大部分语言环境下包含1月4日的那一周作为一年的第一周。所以就会出现虽然是上一年,按周算却是下一年的情况
       * @return {Number} 返回按周计算的年数
       */
      proto.weekYear = function () {
        const month = this.month();
        const weekOfYear = this.week();
        const year = this.year();
        // 月数为 12 ,但周数却为1,说明要算到下一年中,就给返回年数加1
        if (weekOfYear === 1 && month === 11) {
          return year + 1;
        }
        return year;
      };
    };
    

    下篇继续分析插件。


    前端记事本,不定期更新,欢迎关注!

    • 微信公众号: 林景宜的记事本
    • 博客:林景宜的记事本
    • 掘金专栏:林景宜的记事本
    • 知乎专栏: 林景宜的记事本
    • Github: MageeLin


    起源地下载网 » dayjs源码解析(三):插件(上)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元