dayjs 分享

<p style='text-align: center; color: #599FF0;'>Dayjs </p>

分享內(nèi)容

  1. 框架結(jié)構(gòu)
  2. 涉及技術(shù)
  3. 部分代碼解讀
  4. 總結(jié)

概述

Day.js 是一個輕量的處理時間和日期的 JavaScript 庫,和 Moment.js 的 API 設(shè)計保持完全一樣. 如果你曾經(jīng)用過 Moment.js, 那么你已經(jīng)知道如何使用 Day.js

優(yōu)點

??  和Moment.js有著相同的API和模式
??  不可變、持久性
??  提供鏈?zhǔn)秸{(diào)用
??  國際化標(biāo)準(zhǔn)
??  超小的壓縮體積,僅僅有2kb左右
??  極大多數(shù)的瀏覽器兼容

目錄:

    build/                              -> 構(gòu)建工具
        index.js                         -> 入口文件
        rollup.config.js             -> rollup配置文件
    dosc/                               -> 文檔
        en                               -> 英文
        ...                                -> 等等
    src/                                   -> 源代碼
        locale/                        -> 各種語言
            zh-cn.js                     -> 中文
            ...                            -> 等等
        plugin/                       -> 內(nèi)部提供插件
            advancedFormat/  -> 特色格式化
                index.js                 -> index.js
            buddhistEra/            -> 支持農(nóng)歷
                index.js                 -> ..
            isLeapYear/           -> 是否為閏年
                index.js                 -> ..
            relativeTime/           -> 相對時間方法集合,包括 to、from、toNow、fromNow
                index.js                 -> ..
        constant.js                 -> 默認(rèn)常量,包括時間單位,正則
        index.js                         -> 主文件
        util.js                          -> 工具方法
    test/                                 -> jest自動化測試工具
        ...                                -> 測試用例
    .babelrc                             -> babel配置
    .eslintrc.json                    -> eslint配置
    .gitignore                        -> git忽略
    .npmignore                     -> npm忽略
    .travis.yml                      -> travis配置
    CHANGELOG.md              -> 自動輸出的更新日志
    CONTRIBUTING.md       -> 貢獻(xiàn)文檔
    LICENSE                         -> 協(xié)議
    README.md                    -> readme
    index.d.ts                        -> ts靜態(tài)檢查聲明文件
    karma.saucs.conf.js        -> karma配置文件
    package.json                     -> 包配置

涉及技術(shù):

名稱 概述
rollup JavaScript 模塊打包器,dayjs中僅僅用來打包,并沒有用到太多的rollup特性,比如 Tree Shaking
jest JavaScript 測試框架
Karma 測試過程管理工具,通常用于瀏覽器兼容
travis 持續(xù)集成服務(wù),與github綁定,倉庫中只要有新的代碼,就會自動抓取。然后,提供一個運行環(huán)境,執(zhí)行測試,構(gòu)建,并且部署到服務(wù)器。
semantic-release 版本管理,自動化發(fā)布
typescript javascript超集,用來提供類型檢查
eslint 代碼檢查
bebel 代碼編譯

rollup配置:

// rollup.config.js
const babel = require('rollup-plugin-babel');
const uglify = require('rollup-plugin-uglify');

module.exports = config => {
    const { input, fileName, name } = config;
    return {
        input: {
            input,
            external: ['dayjs'], // 外部文件,不進(jìn)行打包
            plugins: [
                babel({
                    exclude: 'node_modules/**'
                }),
                uglify()
            ]
        },
        output: {
            file: fileName,
            format: 'umd', // 采用umd規(guī)范
            name: name || 'dayjs',
            // 重命名全局命名
            globals: { 
                dayjs: 'dayjs'
            }
        }
    };
};

// index.js核心代碼
async function build(option) {
    const bundle = await rollup.rollup(option.input);
    await bundle.write(option.output);
}

(async () => {
    try {
        // 打包 locale
        const locales = await promisifyReadDir(
            path.join(__dirname, '../src/locale')
        );
        locales.forEach(l => {
            build(
                configFactory({
                    input: `./src/locale/${l}`,
                    fileName: `./locale/${l}`,
                    name: `dayjs_locale_${formatName(l)}`
                })
            );
        });

        // 打包plugins
        const plugins = await promisifyReadDir(
            path.join(__dirname, '../src/plugin')
        );
        plugins.forEach(l => {
            build(
                configFactory({
                    input: `./src/plugin/${l}`,
                    fileName: `./plugin/${l}`,
                    name: `dayjs_plugin_${formatName(l)}`
                })
            );
        });

        // 打包index
        build(
            configFactory({
                input: './src/index.js',
                fileName: './dayjs.min.js'
            })
        );
    } catch (e) {
        console.error(e); // eslint-disable-line no-console
    }
})();

rollup-cli: https://github.com/a13821190779/rollup-cli


src部分

constant.js:內(nèi)是一些英文語義變量的定義

utils.js:包含一些常用的工具方法。
    
    padStart(string, length, pad): 補(bǔ)充前綴
    padZoneStr(negMinuts): 轉(zhuǎn)換時間格式為hh(hour):mm(minute) => 為了轉(zhuǎn)換格林威治時間
    monthDiff(a, b): 返回月份差,基于a
    absFloor(n):忽略符號的Math.floor
    prettyUnit(units): 統(tǒng)一化單位 units => unit
    isUndefined(s):是否為undefined
//utils.js中的monthDiff,精度 <= 月 
const monthDiff = (a, b) => {
    // function from moment.js in order to keep the same result
    // 代碼優(yōu)化來自moment.js,反而更難理解了。
    
    // 月份差
    const wholeMonthDiff = (b.year() - a.year()) * 12 + (b.month() - a.month());
    
    // 同化年月
    const anchor = a.clone().add(wholeMonthDiff, 'months');
    
    // 比day的大小
    const c = b - anchor < 0;  // => b < anchor
    
        
    // 錨點2
    const anchor2 = a.clone().add(wholeMonthDiff + (c ? -1 : 1), 'months');
    
    // 返回精度至day的時間差
    return Number(
        -(
            wholeMonthDiff +
            (b - anchor) / (c ? anchor - anchor2 : anchor2 - anchor)
        )
    );
};

index.js

    export default dayjs
    let L = 'en' // global locale
    const Ls = {} // global loaded locale
    const isDayjs = d => d instanceof 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
        }
// parseDate方法
    const parseDate = (date) => {
            let reg
            if (date === null) return new Date(NaN) // => Invalid Date
            if (Utils.isUndefined(date)) return new Date()
            if (date instanceof Date) return date
            if ((typeof date === 'string')
                && (/.*[^Z]$/i.test(date)) // Z代表格林威治時間和本地時間之間的時差
                && (reg = date.match(C.REGEX_PARSE))) { // ^(\d{4})-?(\d{1,2})-?(\d{0,2})(.*?(\d{1,2}):(\d{1,2}):(\d{1,2}))?.?(\d{1,3})?$/ 見下圖
// 結(jié)果結(jié)構(gòu) => ["整體", "括號1的匹配", "括號2的匹配", "括號3的匹配", .....]
                return new Date(
                    reg[1], reg[2] - 1, reg[3] || 1,
                    reg[5] || 0, reg[6] || 0, reg[7] || 0, reg[8] || 0
                )
            }
            return new Date(date) // timestamp
        }

// parseLocale方法
const parseLocale = (preset, object, isLocal) => {
            let l
            if (!preset) return null
            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
            }
            if (!isLocal) L = l
            return l
        }

[圖片上傳失敗...(image-e077f8-1531296814278)]

很方便的正則可視化工具

// Dayjs類
    class Dayjs {
            constructor(cfg) {
                this.parse(cfg)
            }

            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
            }

            $utils() {}
            isValid() {}
            isLeapYear() {}
            $compare(that) {}
            isSame(that) {}
            isBefore(that) {}
            isAfter(that) {}
            year() {}
            month() {}
            day() {}
            date() {}
            hour() {}
            minute() {}
            second() 
            millisecond() {}
            unix() {}
            valueOf() {}
            daysInMonth() {}
            $locale() {}
            locale(preset, object) {}
            clone() {}
            toDate() {}
            toArray() {}
            toJSON() {}
            toISOString() {}
            toObject() {}
            toString() {}
            startOf(units, startOf) {}
            endOf(arg) {}
            $set(units, int) {}
            set(string, int) {}
            add(number, units) {}
            subtract(number, string) {}
            format(formatStr) {}

            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)
            }
        }

總結(jié)

  1. 結(jié)構(gòu)簡潔,清晰,在不失可用性的前提下,盡可能的簡化體積
  2. 代碼抽離徹底,無重復(fù),無強(qiáng)耦合
  3. 使用了自動化測試,和版本控制、發(fā)布,大大解放人力

在閱讀過程中,學(xué)到了如何規(guī)劃中小型框架結(jié)構(gòu),如何使用自動化工具,也掌握了關(guān)于時間的一些細(xì)節(jié),比如格林威治時間。
當(dāng)然,dayjs也不是完美的,也有一些地方可以簡化、優(yōu)化,比如 undefined 替換為 void 0 以節(jié)約字節(jié)(rollup會將undefined替換為void 0)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容