easyui js解析字符串_dayjs源码解析(二):Dayjs 类

d68f9f7e370fe85e0e14223d5eba2f40.png

接上篇 —— dayjs 源码解析(一):概念、locale、constant、utils —— 继续解析 dayjs 的源码。

本篇主要分析 dayjs 源码中最核心的部分,也就是 src/index.js 中的 Dayjs 类。

结构

src/index.js 文件的结构还是很清晰的,代码的骨架如下所示,按如下几步来构造:

  1. 导入 constantlocaleutils
  2. 定义全局 locale
  3. 定义实例化 Dayjs 类的方法
  4. 完善 Utils 工具函数
  5. 定义 Dayjs
  6. 丰富 Dayjsprototype
  7. 定义 Dayjs 的 静态方法
// 导入常量、locale 和 工具函数
import * as C from './constant.js';
import en from './locale/en.js';
import U from './utils.js';

// 全局 locale 定义
let L = 'en';
const Ls = {};
Ls[L] = en;

// 实例化 Dayjs 类的方法
const dayjs = function (date, c) {};

// 把几个工具方法同样加到 Utils 工具包中
const isDayjs = (d) => d instanceof Dayjs;
const parseLocale = (preset, object, isLocal) => {};
const wrapper = (date, instance) => {};
const Utils = U;
Utils.l = parseLocale;
Utils.i = isDayjs;
Utils.w = wrapper;

const parseDate = (cfg) => {};

// Dayjs 类
class Dayjs {
  constructor(cfg) {}
  parse(cfg) {}
  init() {}
  $utils() {}
  isValid() {}
  isSame(that, units) {}
  isAfter(that, units) {}
  isBefore(that, units) {}
  $g(input, get, set) {}
  unix() {}
  valueOf() {}
  startOf(units, startOf) {}
  endOf(arg) {}
  $set(units, int) {}
  set(string, int) {}
  get(unit) {}
  add(number, units) {}
  subtract(number, string) {}
  format(formatStr) {}
  utcOffset() {}
  diff(input, units, float) {}
  daysInMonth() {}
  $locale() {}
  locale(preset, object) {}
  clone() {}
  toDate() {}
  toJSON() {}
  toISOString() {}
  toString() {}
}

// 设置 dayjs 和 Dayjs 的原型链,在 prototype 上设置各个单位的取值和设值函数
const proto = Dayjs.prototype;
dayjs.prototype = proto;
[
  ['$ms', C.MS],
  ['$s', C.S],
  ['$m', C.MIN],
  ['$H', C.H],
  ['$W', C.D],
  ['$M', C.M],
  ['$y', C.Y],
  ['$D', C.DATE],
].forEach((g) => {
  proto[g[1]] = function (input) {};
});

// 下面的方法都是静态方法,挂在 Dayjs 类上
dayjs.extend = (plugin, option) => {};
dayjs.locale = parseLocale;
dayjs.isDayjs = isDayjs;
dayjs.unix = (timestamp) => {};

dayjs.en = Ls[L];
dayjs.Ls = Ls;
dayjs.p = {};
export default dayjs;

但是这里有两个不理解的地方@iamkun,猜想可能是为了缩减代码体积:

  1. 为什么要把 constant.jsutils.js 的导出缩写成 CU,感觉增大了理解成本。
// 先导入成 U
import U from './utils.js';
// 使用之前改成 Utils
const Utils = U;
  1. 为什么在 utils.js 中要把 Utils 中的方法缩写,需要专门去看才能知道简写映射的是哪个方法。
// Utils 对象中的映射
const Utils = {
  s: padStart,
  z: padZoneStr,
  m: monthDiff,
  a: absFloor,
  p: prettyUnit,
  u: isUndefined,
  l: parseLocale;
  i: isDayjs;
  w: wrapper;
}

代码解析

下面正式开始分析代码。

locale 相关

全局定义

首先默认导入了 locale/en.js 英文的 locale,然后使用 L 存放当前用的 locale 名字,使用 Ls(locale Storage)存放 locale 对象

import en from './locale/en.js';

let L = 'en'; // 全局 locale
const Ls = {}; // 全局的已加载 locale 映射
Ls[L] = en;

工具补充

定义了一个工具方法 parseLocale。这个方法很有意思,分为以下几种情况来处理:

  • 如果不带参数使用,返回当前使用的 locale 键;
  • 如果带参数使用,
  • 如果 preset 参数是个对象,就解析对象,给 Ls 中添加或修改对应键值对,并把当前使用的 L 设为解析出来的 name;
  • 如果 preset 参数是个字符串,
    • 如果 object 参数不存在,就把 L 设为 preset;
    • 如果 object 参数存在,就在 Ls 中添加或修改对应键值对,再把 L 设为 preset
  • 最后都返回当前使用的 locale 名,也就是 L

再把定义好的 parseLocale 方法补充到 Utils 中。

/**
 * @description: 给全局locale映射添加一个键值对或修改已有键值对
 * @param {String|Object} preset 预设的local对象或名称字符串
 * @param {Object} object locale对象
 * @param {Boolean} isLocal 是否为本地的 locale
 * @return {String} 返回键名
 */
const parseLocale = (preset, object, isLocal) => {
  let l;
  // 不带参数时,返回当前使用的locale键
  if (!preset) return L;
  // 不管preset是对象还是字符串,都去映射键值对
  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;
  }
  // 如果不用本地的locale,就修改L,最后返回l
  if (!isLocal && l) L = l;
  return l || (!isLocal && L);
};

Utils.l = parseLocale;

相关方法

Dayjs 类中,关于 locale 的方法就是下面两个,实例私有方法 $locale 是用来返回当前使用的 locale 对象;实例方法 locale 本质上就是调用了 parseLocale 方法,但是最后返回的是新的改变了 localeDayjs 实例

注意:在 dayjs 中,很多操作都使用到了 clone() 方法来返回 Dayjs 实例,这也是这个库的优点之一。

最后同样把 parseLocale 方法补充到 Dayjs 类 的静态方法中。

class Dayjs {
  constructor(cfg) {
    this.$L = parseLocale(cfg.locale, null, true); // $L 存放当前 locale
  }

  /**
   * @description: 当前使用的 locale 对象
   * @return {Object} locale 对象
   */
  $locale() {
    return Ls[this.$L];
  }

  /**
   * @description: 无参数时返回当前使用的locale对象,有参数时改变并设置locale对象
   * @param {String} preset locale 对象名
   * @param {Object} object locale 对象
   * @return {Object|Dayjs}
   */
  locale(preset, object) {
    // 无参数时返回当前使用的locale对象
    if (!preset) return this.$L;
    const that = this.clone();
    const nextLocaleName = parseLocale(preset, object, true);
    // 有参数时改变 locale(并设置 locale 对象),后返回新 locale 的 Dayjs 实例
    if (nextLocaleName) that.$L = nextLocaleName;
    return that;
  }
}

// ......
// ......
// ......

dayjs.locale = parseLocale;

补充 Utils

上一节和前文中已经分析了一些 Util 工具,这里把它补充完整:

注意:这些工具方法没有统一定义在 utils.js 文件的原因是用到了 index.js 作用域中的一些变量。
import U from './utils.js';
/**
 * @description: 判断对象是否为Dayjs的实例
 * @param {object} d 对象
 * @return {Boolean}
 */
const isDayjs = (d) => d instanceof Dayjs; // eslint-disable-line no-use-before-define

// 已经解析
const parseLocale = (preset, object, isLocal) => {};

/**
 * @description: 实例化Dayjs的方法,如果参数已经是Dayjs的实例,就直接返回
 * @param {Date|Dayjs} date Date或者Dayjs对象
 * @param {Object} c Date或者Dayjs对象
 * @return {Dayjs} 返回一个Dayjs实例
 */
const dayjs = function (date, c) {
  if (isDayjs(date)) {
    return date.clone();
  }
  const cfg = typeof c === 'object' ? c : {};
  cfg.date = date;
  cfg.args = arguments;
  // cfg: {date, args: arguments}
  return new Dayjs(cfg);
};

/**
 * @description: 封装器,根据Date对象和Dayjs实例封装出一个新实例
 * @param {Date} date Date对象
 * @param {Dayjs} instance 已存在的Dayjs实例
 * @return {Dayjs}
 */
const wrapper = (date, instance) =>
  dayjs(date, {
    locale: instance.$L,
    utc: instance.$u,
    x: instance.$x,
    $offset: instance.$offset,
  });

// 把上面写的几个方法同样加到 Utils 工具包中
const Utils = U;
Utils.l = parseLocale;
Utils.i = isDayjs;
Utils.w = wrapper;

这里需要特别关注的是 wrapper 方法,在 Dayjs 类中大量应用了该方法,其实是通过 date原实例封装了一个新实例,新实例和原实例的主要区别就是关联的时间不同。

Dayjs 类

Dayjs 类是整个 dayjs 库的核心,可以给它定义的实例方法分个类,也可以去查看官网的文档分类。

  • 初始化: parseinit
  • 取赋值: getset
  • 操作: addsubtractstartOfendOfutcOffset
  • 显示: formattoDatetoJSONtoISOStringtoString
  • 查询: isValidisSameisAfterisBeforedaysInMonthdiffunixvalueOf

解析都写在了代码的注释里:

class Dayjs {
  constructor(cfg) {
    this.$L = parseLocale(cfg.locale, null, true); // $L 存放当前 locale
    this.parse(cfg); // for plugin
  }

  /**
   * @description: 解析cfg
   * @param {Object} cfg config 配置对象
   */
  parse(cfg) {
    this.$d = parseDate(cfg); // $d 存放 Date 对象
    this.$x = cfg.x || {}; // $x 存放 {$timezone, $localOffset} 时区有关信息
    this.init();
  }

  /**
   * @description: 初始化内部变量
   */
  init() {
    const { $d } = this;
    this.$y = $d.getFullYear(); // 2020
    this.$M = $d.getMonth(); // 11
    this.$D = $d.getDate(); // 8
    this.$W = $d.getDay(); // 2
    this.$H = $d.getHours(); // 7
    this.$m = $d.getMinutes(); // 6
    this.$s = $d.getSeconds(); // 1
    this.$ms = $d.getMilliseconds(); // 425
  }

  /**
   * @description: 返回完备的工具库
   * @return {Object} Utils对象
   * @private
   */
  $utils() {
    return Utils;
  }

  /**
   * @description: 返回 Date 对象是否合规
   * @return {Boolean}
   */
  isValid() {
    return !(this.$d.toString() === C.INVALID_DATE_STRING);
  }

  /**
   * @description: 本实例是否和that提供的日期时间相同
   * @param {Dayjs|String} that 另一个Dayjs实例或者时间字符串
   * @param {String} units 单位
   * @return {Boolean} 只要在单位的时间段内,就算相同
   */
  isSame(that, units) {
    const other = dayjs(that);
    // 用给定单位内本实例的 start 和 end 来夹逼 that 实例
    return this.startOf(units) <= other && other <= this.endOf(units);
  }

  /**
   * @description: 给定单位下,本实例的时间是否晚于that提供的时间
   * @param {Dayjs|String} that 另一个Dayjs实例或者时间字符串
   * @param {String} units 单位
   * @return {Boolean}
   */
  isAfter(that, units) {
    return dayjs(that) < this.startOf(units);
  }

  /**
   * @description: 给定单位下,本实例的时间是否早于that提供的时间
   * @param {Dayjs|String} that 另一个Dayjs实例或者时间字符串
   * @param {String} units 单位
   * @return {Boolean}
   */
  isBefore(that, units) {
    return this.endOf(units) < dayjs(that);
  }

  /**
   * @description: 获取或设置单位
   * @param {Number} input 要设置的值
   * @return {Number|Dayjs} 有input时,设置值并返回实例;无input时,返回对应单位的数值
   */
  $g(input, get, set) {
    // 无参数就get,有参数就set
    if (Utils.u(input)) return this[get];
    return this.set(set, input);
  }

  /**
   * @description: 返回秒做单位的 Unix 时间戳 (10 位数字)
   * @return {Number} 十位时间戳
   */
  unix() {
    return Math.floor(this.valueOf() / 1000);
  }

  /**
   * @description: 根据实例关联的Date对象返回13位时间戳 ms
   * @return {Number} 时间戳 eg.1607404331806
   */
  valueOf() {
    // timezone(hour) * 60 * 60 * 1000 => ms
    return this.$d.getTime();
  }

  /**
   * @description: 根据单位将实例设置到一个时间段的开始
   * @param {String} units 单位
   * @param {Boolean} startOf 标志,true:startOf, false: endOf
   * @return {Dayjs} 返回新的 Dayjs 实例,cfg与原实例相同
   */
  startOf(units, startOf) {
    // 用于切换 startOf 和 endOf
    const isStartOf = !Utils.u(startOf) ? startOf : true;
    const unit = Utils.p(units);
    /**
     * @description: 实例工厂函数,根据月日(参数)和年份(实例)创建新的Dayjs实例
     * @param {Number} d 日
     * @param {Number} m 月
     * @return {Dayjs} 返回新实例,如果 isStartOf 为 false,返回当天的 endOf
     */
    const instanceFactory = (d, m) => {
      const ins = Utils.w(
        this.$u ? Date.UTC(this.$y, m, d) : new Date(this.$y, m, d),
        this
      );
      return isStartOf ? ins : ins.endOf(C.D);
    };
    /**
     * @description: 根据传入的method来返回Dayjs新实例
     * @param {String} method 例如 setHours、setUTCHours
     * @param {Number} slice 截断参数数组
     * @return {Dayjs} 返回新实例,
     */
    const instanceFactorySet = (method, slice) => {
      // 传递给apply的参数数组
      const argumentStart = [0, 0, 0, 0];
      const argumentEnd = [23, 59, 59, 999];
      return Utils.w(
        this.toDate()[method].apply(
          this.toDate('s'),
          (isStartOf ? argumentStart : argumentEnd).slice(slice)
        ),
        this
      );
    };

    // 获取day、month、date
    const { $W, $M, $D } = this;
    const utcPad = `set${this.$u ? 'UTC' : ''}`;
    switch (unit) {
      // year,返回1月1日或者11月31日的Dayjs实例
      case C.Y:
        return isStartOf ? instanceFactory(1, 0) : instanceFactory(31, 11);
      // month,返回{month+1}月1日或本月最后一天
      case C.M:
        return isStartOf ? instanceFactory(1, $M) : instanceFactory(0, $M + 1); // 0, $M + 1可获得上月的最后一天,避免29 30 31的区别
      // week 返回周的第一天或最后一天
      case C.W: {
        const weekStart = this.$locale().weekStart || 0;
        const gap = ($W < weekStart ? $W + 7 : $W) - weekStart;
        return instanceFactory(isStartOf ? $D - gap : $D + (6 - gap), $M);
      }
      // day date 返回一天的第一个小时或者最后一个小时
      case C.D:
      case C.DATE:
        return instanceFactorySet(`${utcPad}Hours`, 0);
      // hour 返回一小时的第一分钟或最后一分钟
      case C.H:
        return instanceFactorySet(`${utcPad}Minutes`, 1);
      // minute 返回一分钟的第一秒或最后一秒
      case C.MIN:
        return instanceFactorySet(`${utcPad}Seconds`, 2);
      // second 返回一秒钟的第一毫秒或最后一毫秒
      case C.S:
        return instanceFactorySet(`${utcPad}Milliseconds`, 3);
      // 默认直接返回本实例
      default:
        return this.clone();
    }
  }

  /**
   * @description: 根据单位将实例设置到一个时间段的结束
   * @param {String} units 单位
   * @return {Dayjs} 返回新的 Dayjs 实例,cfg与原实例相同
   */
  endOf(arg) {
    return this.startOf(arg, false);
  }

  /**
   * @description: 私有 setter,两个参数分别是要更新的单位和数值,调用后会返回一个修改后的新实例。
   * @param {String} units 单位
   * @param {Number} int 值
   * @return {Dayjs} 返回修改后的实例
   * @private
   */
  $set(units, int) {
    // 根据 units 处理函数名
    const unit = Utils.p(units);
    const utcPad = `set${this.$u ? 'UTC' : ''}`;
    const name = {
      [C.D]: `${utcPad}Date`,
      [C.DATE]: `${utcPad}Date`,
      [C.M]: `${utcPad}Month`,
      [C.Y]: `${utcPad}FullYear`,
      [C.H]: `${utcPad}Hours`,
      [C.MIN]: `${utcPad}Minutes`,
      [C.S]: `${utcPad}Seconds`,
      [C.MS]: `${utcPad}Milliseconds`,
    }[unit];
    const arg = unit === C.D ? this.$D + (int - this.$W) : int;

    // 把 $d,也就是关联的 Date 对象设为对应 int
    if (unit === C.M || unit === C.Y) {
      // clone is for badMutable plugin
      const date = this.clone().set(C.DATE, 1);
      date.$d[name](arg);
      date.init();
      this.$d = date.set(C.DATE, Math.min(this.$D, date.daysInMonth())).$d;
    } else if (name) this.$d[name](arg);

    // 重新初始化
    this.init();
    return this;
  }

  /**
   * @description: 通用的 setter,两个参数分别是要更新的单位和数值,调用后会返回一个修改后的新实例。
   * @param {String} string 单位
   * @param {Number} int 值
   * @return {Dayjs} 返回修改后的实例
   */
  set(string, int) {
    return this.clone().$set(string, int);
  }

  /**
   * @description: 从实例中获取相应信息的通用 getter。
   * @param {String} unit
   * @return {Number} 返回对应单位的值
   */
  get(unit) {
    return this[Utils.p(unit)]();
  }

  /**
   * @description: 根据单位和值,给当前关联的Date对象增加时间
   * @param {Number} number 增加的数值
   * @param {String} units 单位
   * @return {Dayjs} 返回新的Dayjs对象
   */
  add(number, units) {
    number = Number(number); // eslint-disable-line no-param-reassign
    const unit = Utils.p(units);
    /**
     * @description: 专门给 week 和 date 用的增加时间的工具函数
     * @param {Number} n 基础单位的天数 例如 week 是 7
     * @return {Dayjs} 返回新的 Dayjs 对象
     */
    const instanceFactorySet = (n) => {
      const d = dayjs(this);
      return Utils.w(d.date(d.date() + Math.round(n * number)), this);
    };
    // 月
    if (unit === C.M) {
      return this.set(C.M, this.$M + number);
    }
    // 年
    if (unit === C.Y) {
      return this.set(C.Y, this.$y + number);
    }
    // 日
    if (unit === C.D) {
      return instanceFactorySet(1);
    }
    // 周
    if (unit === C.W) {
      return instanceFactorySet(7);
    }
    const step =
      {
        [C.MIN]: C.MILLISECONDS_A_MINUTE, // 分钟
        [C.H]: C.MILLISECONDS_A_HOUR, // 小时
        [C.S]: C.MILLISECONDS_A_SECOND, // 秒
      }[unit] || 1; // ms

    const nextTimeStamp = this.$d.getTime() + number * step;
    return Utils.w(nextTimeStamp, this);
  }

  /**
   * @description: 根据单位和值,给当前关联的Date对象减少时间
   * @param {Number} number 减少的数值
   * @param {String} units 单位
   * @return {Dayjs} 返回新的Dayjs对象
   */
  subtract(number, string) {
    return this.add(number * -1, string);
  }

  /**
   * @description: 根据模板返回对应格式的时间字符串
   * @param {String} formatStr 模板字符串
   * @return {String} 对应格式的时间字符串
   */
  format(formatStr) {
    if (!this.isValid()) return C.INVALID_DATE_STRING;

    // 整理出所需的各种变量和方法
    const str = formatStr || C.FORMAT_DEFAULT;
    const zoneStr = Utils.z(this);
    const locale = this.$locale();
    const { $H, $m, $M } = this;
    const { weekdays, months, meridiem } = locale;

    /**
     * @description: 返回对应缩写的字符串,可自适应
     * @param {Array} arr 几月或者周几的缩写数组 ["1月", "2月", "3月"...]
     * @param {Number} index 索引
     * @param {Array} full 几月或者周几的非缩写数组 ["一月", "二月", "三月"...]
     * @param {Number} length 返回结果的字符数
     * @return {String} 对应缩写的字符串
     */
    const getShort = (arr, index, full, length) =>
      (arr && (arr[index] || arr(this, str))) || full[index].substr(0, length);

    /**
     * @description: 获取固定长度的小时表示
     * @param {Number} num 小时的长度
     * @return {String} 固定长度的小时表示
     */
    const get$H = (num) => Utils.s($H % 12 || 12, num, '0');

    /**
     * @description: 根据时和分区分时间段(上午、下午)
     * @param {Number} hour 时
     * @param {Number} minute 分
     * @param {Boolean} isLowercase 是否小写,默认false
     * @return {String} 时间段 例如 AM
     */
    const meridiemFunc =
      meridiem ||
      ((hour, minute, isLowercase) => {
        const m = hour < 12 ? 'AM' : 'PM';
        return isLowercase ? m.toLowerCase() : m;
      });

    // 不同的模板对应的格式转换
    const matches = {
      YY: String(this.$y).slice(-2),
      YYYY: this.$y,
      M: $M + 1,
      MM: Utils.s($M + 1, 2, '0'),
      MMM: getShort(locale.monthsShort, $M, months, 3),
      MMMM: getShort(months, $M),
      D: this.$D,
      DD: Utils.s(this.$D, 2, '0'),
      d: String(this.$W),
      dd: getShort(locale.weekdaysMin, this.$W, weekdays, 2),
      ddd: getShort(locale.weekdaysShort, this.$W, weekdays, 3),
      dddd: weekdays[this.$W],
      H: String($H),
      HH: Utils.s($H, 2, '0'),
      h: get$H(1),
      hh: get$H(2),
      a: meridiemFunc($H, $m, true),
      A: meridiemFunc($H, $m, false),
      m: String($m),
      mm: Utils.s($m, 2, '0'),
      s: String(this.$s),
      ss: Utils.s(this.$s, 2, '0'),
      SSS: Utils.s(this.$ms, 3, '0'),
      Z: zoneStr, // 'ZZ' logic below
    };

    // /[([^]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g
    // 最后进行了整个字符串模板的内容替换
    return str.replace(
      C.REGEX_FORMAT,
      (match, $1) => $1 || matches[match] || zoneStr.replace(':', '')
    ); // 'ZZ'
  }

  /**
   * @description: 返回分钟级的时区偏移量 (精度15分钟)
   * @return {Number} 时区偏移量 分钟
   */
  utcOffset() {
    // 由于 FF24 bug,我们把时区偏移近似到 15 分钟
    // https://github.com/moment/moment/pull/1871
    return -Math.round(this.$d.getTimezoneOffset() / 15) * 15;
  }

  /**
   * @description: 返回指定单位下两个日期时间之间的差异。
   * @param {String} input 输入的时间
   * @param {String} units 单位
   * @param {Boolean} float 是否需要取整
   * @return {Number} 返回对应单位下的时间差
   */
  diff(input, units, float) {
    const unit = Utils.p(units);
    // input封装成实例
    const that = dayjs(input);
    const zoneDelta =
      (that.utcOffset() - this.utcOffset()) * C.MILLISECONDS_A_MINUTE;
    // 毫秒差
    const diff = this - that;
    // 月份差
    let result = Utils.m(this, that);

    // 区分不同的单位
    result =
      {
        [C.Y]: result / 12,
        [C.M]: result,
        [C.Q]: result / 3,
        [C.W]: (diff - zoneDelta) / C.MILLISECONDS_A_WEEK,
        [C.D]: (diff - zoneDelta) / C.MILLISECONDS_A_DAY,
        [C.H]: diff / C.MILLISECONDS_A_HOUR,
        [C.MIN]: diff / C.MILLISECONDS_A_MINUTE,
        [C.S]: diff / C.MILLISECONDS_A_SECOND,
      }[unit] || diff; // milliseconds

    // 是否取整
    return float ? result : Utils.a(result);
  }

  /**
   * @description: 返回实例所在月的总天数
   * @return {Number} 天数
   */
  daysInMonth() {
    return this.endOf(C.M).$D;
  }

  /**
   * @description: 当前使用的 locale 对象
   * @return {Object} locale 对象
   */
  $locale() {
    return Ls[this.$L];
  }

  /**
   * @description: 无参数时返回当前使用的locale对象,有参数时改变并设置locale对象
   * @param {String} preset locale 对象名
   * @param {Object} object locale 对象
   * @return {Object|Dayjs}
   */
  locale(preset, object) {
    // 无参数时返回当前使用的locale对象
    if (!preset) return this.$L;
    const that = this.clone();
    const nextLocaleName = parseLocale(preset, object, true);
    // 有参数时改变 locale(并设置 locale 对象),后返回新 locale 的 Dayjs 实例
    if (nextLocaleName) that.$L = nextLocaleName;
    return that;
  }

  /**
   * @description: 克隆本实例并返回
   * @return {Dayjs} 返回新的 Dayjs 实例
   */
  clone() {
    return Utils.w(this.$d, this);
  }

  /**
   * @description: 返回实例对应的 Date 对象
   * @return {Date} Date 对象
   */
  toDate() {
    return new Date(this.valueOf());
  }

  /**
   * @description: 返回ISO格式的字符串(YYYY-MM-DDTHH:mm:ss.sssZ),不太懂
   * @return {String} UTC(协调世界时)例如 2020-12-09T05:14:04.670Z
   */
  toJSON() {
    return this.isValid() ? this.toISOString() : null;
  }

  /**
   * @description: 返回ISO格式的字符串(YYYY-MM-DDTHH:mm:ss.sssZ)
   * @return {String} UTC(协调世界时)例如 2020-12-09T05:14:04.670Z
   */
  toISOString() {
    // ie 8 return
    // new Dayjs(this.valueOf() + this.$d.getTimezoneOffset() * 60000)
    // .format('YYYY-MM-DDTHH:mm:ss.SSS[Z]')
    return this.$d.toISOString();
  }

  /**
   * @description: 返回一个字符串
   * @return {String} 例如"Wed, 09 Dec 2020 05:16:39 GMT"
   */
  toString() {
    return this.$d.toUTCString();
  }
}

原型链

正常来说,定义在实例中的方法就应该在原型链上,但是有几个与时间有关的 setter/getter 方法比较相似,所以就单独拿出了原型链写在了上面。

const proto = Dayjs.prototype;
dayjs.prototype = proto;

// 在prototype上设置各个单位的取值和设值函数
[
  ['$ms', C.MS], // Dayjs.prototype.millisecond
  ['$s', C.S], // Dayjs.prototype.second
  ['$m', C.MIN], // Dayjs.prototype.minute
  ['$H', C.H], // Dayjs.prototype.hour
  ['$W', C.D], // Dayjs.prototype.day
  ['$M', C.M], // Dayjs.prototype.month
  ['$y', C.Y], // Dayjs.prototype.year
  ['$D', C.DATE], // Dayjs.prototype.date
].forEach((g) => {
  proto[g[1]] = function (input) {
    // g[0]是实例上的值,g[1]是字符串(例如date)
    return this.$g(input, g[0], g[1]);
  };
});

这几个方法全都是不传参数就 getter,传参数就 setter

静态属性

还有一些方法和属性挂在了 dayjs 函数对象上,最核心的是加载插件和加载 locale 的方法,几个方法的用法都能在官方文档中找到。

// 下面的方法都是静态方法,挂在dayjs 函数对象上
/**
 * @description: 挂载插件
 * @param {*} plugin 插件
 * @param {*} option 插件选项
 * @return {dayjs function} 返回 dayjs 函数对象
 */
dayjs.extend = (plugin, option) => {
  // 同一个插件只挂载一次
  if (!plugin.$i) {
    plugin(option, Dayjs, dayjs); //挂载
    plugin.$i = true;
  }
  return dayjs;
};

dayjs.locale = parseLocale;

dayjs.isDayjs = isDayjs;

/**
 * @description: 解析传入的一个秒做单位的 Unix 时间戳 (10 位数字),返回一个 Dayjs 实例
 * @param {Number} timestamp 10位的时间戳
 * @return {Dayjs} 返回一个 Dayjs 实例
 */
dayjs.unix = (timestamp) => dayjs(timestamp * 1e3);

dayjs.en = Ls[L];
dayjs.Ls = Ls;
dayjs.p = {};

如果看到这里对 dayjs 函数对象Dayjs 类原型的关系很懵,可以参考下图,最后形成的关系如下图所示:

383e6bd3d2e61921e2d5dae05ab256f0.png

总结

如果不看插件部分的话,其实 dayjs 库的核心已经解析完成,看一下默认生成 dayjs 实例长什么样子:

{
  $D: 15,
  $H: 16,
  $L: "en",
  $M: 11,
  $W: 2,
  $d: Tue Dec 15 2020 16:33:49 GMT+0800 (中国标准时间) {},
  $m: 33,
  $ms: 216,
  $s: 49,
  $x: {},
  $y: 2020,
  __proto__: {
    date: ƒ (input),
    day: ƒ (input),
    hour: ƒ (input),
    millisecond: ƒ (input),
    minute: ƒ (input),
    month: ƒ (input),
    second: ƒ (input),
    year: ƒ (input),
    $g: ƒ $g(input, get, set),
    $locale: ƒ $locale(),
    $set: ƒ $set(units, int),
    $utils: ƒ $utils(),
    add: add(number, units),
    clone: ƒ clone(),
    constructor: class Dayjs,
    daysInMonth: ƒ daysInMonth(),
    diff: ƒ diff(input, units, float),
    endOf: ƒ endOf(arg),
    format: format(formatStr),
    get: ƒ get(unit),
    init: ƒ init(),
    isAfter: ƒ isAfter(that, units),
    isBefore: ƒ isBefore(that, units),
    isSame: ƒ isSame(that, units),
    isValid: ƒ isValid(),
    locale: ƒ locale(preset, object),
    parse: ƒ parse(cfg),
    set: ƒ set(string, int),
    startOf: startOf(units, startOf),
    subtract: ƒ subtract(number, string),
    toDate: ƒ toDate(),
    toISOString: ƒ toISOString(),
    toJSON: ƒ toJSON(),
    toString: ƒ toString(),
    unix: ƒ unix(),
    utcOffset: ƒ utcOffset(),
    valueOf: valueOf(),
  }
}

实例本身的属性是一些与时间相关的属性,各种操作方法都在原型 __proto__ 上。

本节完成,下一节开始解析 dayjs 的插件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值