摘要:接下来,本篇将解读一下中的构造函数。最后将传入类的构造函数,生成一个对象,作为函数的返回值给返回了。参数通过上面对参数的分析知道了参数其实是当参数为实例对象时,最后又会调用函数,此时才会传入参数。
前言
上一篇 dayjs 源码解析(二)(目录结构)介绍了 dayjs 的源码目录结构。接下来,本篇将解读一下 index.js 中的 dayjs 构造函数。
dayjs 构造函数
// d 是否为 Dayjs 的实例对象
const isDayjs = d => d instanceof Dayjs
// dayjs 函数,用于返回新的 Dayjs 实例对象的函数
const dayjs = (date, c) => {
// 若date 为 Dayjs 的实例对象,则返回克隆的 Dayjs 实例对象(immutable)
if (isDayjs(date)) {
return date.clone()
}
const cfg = c || {}
cfg.date = date
return new Dayjs(cfg)
}
// Dayjs 类
class Dayjs {
//...
}
翻看 第一篇 中介绍的 api,有哪些 api 会用到 dayjs() 构造函数呢?在构造一个新的 Dayjs 实例对象的时候会用到 dayjs()。
即 “解析类中的构造和克隆”
首先看参数 date。在上面的 api 中,我们可以看到,date 参数可以有五种类型,这五种类型可以分为两类:
一、非 Dayjs 实例对象:
不传参,即 date 为undefined
date 为 ISO 8601 标准格式的字符串
date 为 unix 时间戳
date 为 JavaScript 原生的 Date 实例对象
二、Dayjs 实例对象
date 为 Dayjs 实例对象
当 date 参数为 “非 Dayjs 实例对象” 时
// d 是否为 Dayjs 的实例对象
const isDayjs = d => d instanceof Dayjs
// dayjs 函数,用于返回新的 Dayjs 实例对象的函数
const dayjs = (date, c) => {
// 若date 为 Dayjs 的实例对象,则返回克隆的 Dayjs 实例对象(immutable)
if (isDayjs(date)) {
return date.clone()
}
const cfg = c || {}
cfg.date = date
return new Dayjs(cfg)
}
// Dayjs 类
class Dayjs {
//...
}
// 调用 dayjs 函数
dayjs("2018-7-1")
此时,c 参数(后面会讲 c 参数的作用)为空,所以 cfg 变量被赋值为一个 空对象{}。
然后将传入的 date 参数赋值给 cfg 对象的 date 属性。最后将 cfg 传入 Dayjs 类的构造函数,生成一个 Dayjs 对象,作为 dayjs() 函数的返回值给返回了。
所以,最终 dayjs() 函数返回的是一个 Dayjs 实例对象。
当 date 参数为 “Dayjs 实例对象” 时
// d 是否为 Dayjs 的实例对象
const isDayjs = d => d instanceof Dayjs
// dayjs 函数,用于返回新的 Dayjs 实例对象的函数
const dayjs = (date, c) => {
// 若date 为 Dayjs 的实例对象,则返回克隆的 Dayjs 实例对象(immutable)
if (isDayjs(date)) {
return date.clone()
}
const cfg = c || {}
cfg.date = date
return new Dayjs(cfg)
}
// Dayjs 类
class Dayjs {
//...
}
// 调用 dayjs 函数
dayjs(dayjs())
因为传入的 date 参数为 Dayjs 实例对象,所以 isDayjs(date) 返回 true,然后调用 date.clone() 方法。
通过阅读 Dayjs 类的代码,可知道,clone() 不是挂载到 Dayjs 实例对象上的,而是挂载到 Dayjs 的原型对象上的(date 通过原型链找到 clone() 方法,然后进行调用):
class Dayjs {
//...other code
clone() {
return wrapper(this.toDate(), this)
}
// 转换为新的原生的 JavaScript Date 对象
toDate() {
return new Date(this.$d)
}
//...other code
}
调用 clone() 方法时,会先调用 this.toDate() 方法。this.toDate() 方法返回一个新的 JavaScript 原生的 Date 实例对象(其中的 this.$d 为 date 参数中的 JavaScript 原生的 Date 实例对象,在下一篇 Dayjs 类 中会讲到)。
然后将这个新的 Date 实例对象以及 this(date 参数)作为 wrapper 的参数,调用 wrapper() 函数:
// date 为 JavaScript 原生的 Date 对象;instance 为 Dayjs 实例对象
const wrapper = (date, instance) => dayjs(date, { locale: instance.$L })
在 wrapper() 函数中,又反过来调用 dayjs() 函数。在这里,传入了 date(date 为 JavaScript 原生的 Date 实例对象)和 c(c 为一个带有 locale 属性的对象,locale 的值为 Dayjs 实例对象的 $L 的值)
最后再返回来看 dayjs() 函数:
// dayjs 函数,用于返回新的 Dayjs 实例对象的函数
const dayjs = (date, c) => {
// 若date 为 Dayjs 的实例对象,则返回克隆的 Dayjs 实例对象(immutable)
if (isDayjs(date)) {
return date.clone()
}
const cfg = c || {}
cfg.date = date
return new Dayjs(cfg)
}
此时传入的 date 参数为 JavaScript 原生的 Date 实例对象,c 为带有 locale 属性的对象。
最后,dayjs() 调用,返回了一个新的 Dayjs 实例对象。
所以,当 date 参数为 Dayjs 实例对象时,在 dayjs() 函数内部,最后又会调用 dayjs() 函数,此时传入 dayjs() 函数的参数为两个:
date(新的原生的 JavaScript Date 实例对象)
c(包含 locale 属性的对象,locale 的值为上一个 Dayjs 实例对象所用的语言,是一个字符串类型)
此时和 date 参数为 “非 Dayjs 实例对象” 时是一样的执行,只不过多了一个参数 c 罢了。
参数 c通过上面对参数 date 的分析知道了参数 c 其实是当 date 参数为 Dayjs 实例对象时,最后又会调用 dayjs() 函数,此时才会传入参数 c。
参数 c 为一个包含 locale 属性的对象(locale 的值为上一个 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
}
const wrapper = (date, instance) => dayjs(date, { locale: instance.$L })
class Dayjs {
constructor(cfg) {
this.parse(cfg) // for plugin
}
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
}
// eslint-disable-next-line class-methods-use-this
$utils() {
return Utils
}
isValid() {
return !(this.$d.toString() === "Invalid Date")
}
isLeapYear() {
return ((this.$y % 4 === 0) && (this.$y % 100 !== 0)) || (this.$y % 400 === 0)
}
$compare(that) {
return this.valueOf() - dayjs(that).valueOf()
}
isSame(that) {
return this.$compare(that) === 0
}
isBefore(that) {
return this.$compare(that) < 0
}
isAfter(that) {
return this.$compare(that) > 0
}
year() {
return this.$y
}
month() {
return this.$M
}
day() {
return this.$W
}
date() {
return this.$D
}
hour() {
return this.$H
}
minute() {
return this.$m
}
second() {
return this.$s
}
millisecond() {
return this.$ms
}
unix() {
return Math.floor(this.valueOf() / 1000)
}
valueOf() {
// timezone(hour) * 60 * 60 * 1000 => ms
return this.$d.getTime()
}
startOf(units, startOf) { // startOf -> endOf
const isStartOf = !Utils.isUndefined(startOf) ? startOf : true
const unit = Utils.prettyUnit(units)
const instanceFactory = (d, m) => {
const ins = wrapper(new Date(this.$y, m, d), this)
return isStartOf ? ins : ins.endOf(C.D)
}
const instanceFactorySet = (method, slice) => {
const argumentStart = [0, 0, 0, 0]
const argumentEnd = [23, 59, 59, 999]
return wrapper(this.toDate()[method].apply( // eslint-disable-line prefer-spread
this.toDate(),
isStartOf ? argumentStart.slice(slice) : argumentEnd.slice(slice)
), this)
}
switch (unit) {
case C.Y:
return isStartOf ? instanceFactory(1, 0) :
instanceFactory(31, 11)
case C.M:
return isStartOf ? instanceFactory(1, this.$M) :
instanceFactory(0, this.$M + 1)
case C.W:
return isStartOf ? instanceFactory(this.$D - this.$W, this.$M) :
instanceFactory(this.$D + (6 - this.$W), this.$M)
case C.D:
case C.DATE:
return instanceFactorySet("setHours", 0)
case C.H:
return instanceFactorySet("setMinutes", 1)
case C.MIN:
return instanceFactorySet("setSeconds", 2)
case C.S:
return instanceFactorySet("setMilliseconds", 3)
default:
return this.clone()
}
}
endOf(arg) {
return this.startOf(arg, false)
}
$set(units, int) { // private set
const unit = Utils.prettyUnit(units)
switch (unit) {
case C.DATE:
this.$d.setDate(int)
break
case C.M:
this.$d.setMonth(int)
break
case C.Y:
this.$d.setFullYear(int)
break
case C.H:
this.$d.setHours(int)
break
case C.MIN:
this.$d.setMinutes(int)
break
case C.S:
this.$d.setSeconds(int)
break
case C.MS:
this.$d.setMilliseconds(int)
break
default:
break
}
this.init()
return this
}
set(string, int) {
return this.clone().$set(string, int)
}
add(number, units) {
number = Number(number) // eslint-disable-line no-param-reassign
const unit = Utils.prettyUnit(units)
const instanceFactory = (u, n) => {
const date = this.set(C.DATE, 1).set(u, n + number)
return date.set(C.DATE, Math.min(this.$D, date.daysInMonth()))
}
if (unit === C.M) {
return instanceFactory(C.M, this.$M)
}
if (unit === C.Y) {
return instanceFactory(C.Y, this.$y)
}
let step
switch (unit) {
case C.MIN:
step = C.MILLISECONDS_A_MINUTE
break
case C.H:
step = C.MILLISECONDS_A_HOUR
break
case C.D:
step = C.MILLISECONDS_A_DAY
break
case C.W:
step = C.MILLISECONDS_A_WEEK
break
case C.S:
step = C.MILLISECONDS_A_SECOND
break
default: // ms
step = 1
}
const nextTimeStamp = this.valueOf() + (number * step)
return wrapper(nextTimeStamp, this)
}
subtract(number, string) {
return this.add(number * -1, string)
}
format(formatStr) {
const str = formatStr || C.FORMAT_DEFAULT
const zoneStr = Utils.padZoneStr(this.$d.getTimezoneOffset())
const locale = this.$locale()
const {
weekdays, months
} = locale
const getShort = (arr, index, full, length) => (
(arr && arr[index]) || full[index].substr(0, length)
)
return str.replace(C.REGEX_FORMAT, (match) => {
if (match.indexOf("[") > -1) return match.replace(/[|]/g, "")
switch (match) {
case "YY":
return String(this.$y).slice(-2)
case "YYYY":
return String(this.$y)
case "M":
return String(this.$M + 1)
case "MM":
return Utils.padStart(this.$M + 1, 2, "0")
case "MMM":
return getShort(locale.monthsShort, this.$M, months, 3)
case "MMMM":
return months[this.$M]
case "D":
return String(this.$D)
case "DD":
return Utils.padStart(this.$D, 2, "0")
case "d":
return String(this.$W)
case "dd":
return getShort(locale.weekdaysMin, this.$W, weekdays, 2)
case "ffffd":
return getShort(locale.weekdaysShort, this.$W, weekdays, 3)
case "ffffdd":
return weekdays[this.$W]
case "H":
return String(this.$H)
case "HH":
return Utils.padStart(this.$H, 2, "0")
case "h":
case "hh":
if (this.$H === 0) return 12
return Utils.padStart(this.$H < 13 ? this.$H : this.$H - 12, match === "hh" ? 2 : 1, "0")
case "a":
return this.$H < 12 ? "am" : "pm"
case "A":
return this.$H < 12 ? "AM" : "PM"
case "m":
return String(this.$m)
case "mm":
return Utils.padStart(this.$m, 2, "0")
case "s":
return String(this.$s)
case "ss":
return Utils.padStart(this.$s, 2, "0")
case "SSS":
return Utils.padStart(this.$ms, 3, "0")
case "Z":
return zoneStr
default: // "ZZ"
return zoneStr.replace(":", "")
}
})
}
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)
}
daysInMonth() {
return this.endOf(C.M).$D
}
$locale() { // get locale object
return Ls[this.$L]
}
locale(preset, object) {
const that = this.clone()
that.$L = parseLocale(preset, object, true)
return that
}
clone() {
return wrapper(this.toDate(), this)
}
toDate() {
return new Date(this.$d)
}
toArray() {
return [
this.$y,
this.$M,
this.$D,
this.$H,
this.$m,
this.$s,
this.$ms
]
}
toJSON() {
return this.toISOString()
}
toISOString() {
// ie 8 return
// new Dayjs(this.valueOf() + this.$d.getTimezoneOffset() * 60000)
// .format("YYYY-MM-DDTHH:mm:ss.SSS[Z]")
return this.toDate().toISOString()
}
toObject() {
return {
years: this.$y,
months: this.$M,
date: this.$D,
hours: this.$H,
minutes: this.$m,
seconds: this.$s,
milliseconds: this.$ms
}
}
toString() {
return this.$d.toUTCString()
}
}
下一篇:dayjs 源码解析(四)(Dayjs 类)
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/95861.html
摘要:前言上一篇源码解析一,介绍了一下的,知道了如何使用。本篇,介绍项目的目录结构。源码解析三构造函数 前言 上一篇 dayjs 源码解析(一)(api),介绍了一下 dayjs 的 api,知道了如何使用 dayjs。本篇,介绍 dayjs 项目的目录结构。 目录结构 showImg(https://segmentfault.com/img/bVbcW0Q?w=229&h=832); 在 ...
摘要:下面,我将自己阅读的源码的过程记录下来。阅读库的代码,首先先要知道这个库的作用是一个轻量的时间日期处理库,其用法和完全一样。介绍首先,阅读的源码,我们应该从的入手。对象是不可变的,即所有改变的操作都会返回一个新的实例。 前言 作为一个程序员,阅读别人优秀代码是提升自己技术能力的一个很好的方法。下面,我将自己阅读 dayjs(v1.6.10)的源码的过程记录下来。 阅读库的代码,首先先要...
摘要:前言上一篇源码解析三构造函数介绍了的源码中的函数。接下来,本篇将解读一下中的类。首先,我们看的构造函数,该构造函数调用了实例方法,传入参数在上一篇有讲到。下一篇源码解析五插件详解 前言 上一篇 dayjs 源码解析(三)(dayjs 构造函数)介绍了 dayjs 的源码中的 dayjs 函数。接下来,本篇将解读一下 index.js 中的 Dayjs 类。 class Dayjs { ...
摘要:前言上一篇源码解析四类介绍了的源码目录结构。接下来,本篇将分析一下中插件功能的用法源码以及如何编写自己的插件。并且,可以通过插件选项,来对插件进行配置。 前言 上一篇 dayjs 源码解析(四)(Dayjs 类)介绍了 dayjs 的源码目录结构。接下来,本篇将分析一下 dayjs 中插件功能的用法、源码以及如何编写自己的 dayjs 插件。 dayjs 插件用法 dayjs 的插件,...
showImg(https://segmentfault.com/img/bV9wV7?w=1952&h=712);Moment.js 是一个大而全的 JS 时间库,很大地方便了我们处理日期和时间。但是 Moment.js太重了(200k+ with locals),可能一般项目也只使用到了她几个常用的API。虽然社区也有几个轻量的时间库,要想迁移过去又会增加新的学习和迁移成本。 如果能有一个和 ...
阅读 2273·2019-08-30 15:54
阅读 3875·2019-08-29 13:07
阅读 3395·2019-08-29 12:39
阅读 2087·2019-08-26 12:13
阅读 1816·2019-08-23 18:31
阅读 2427·2019-08-23 18:05
阅读 2133·2019-08-23 18:00
阅读 1319·2019-08-23 17:15