Moment.js:moment(string,format)源码解析

2021-02-14 02:17

阅读:769

YPE html>

标签:har   轻量级   port   token   maps   fse   actor   and   const   

1 概述

  • 最近被Safari浏览坑了两次:new Date(‘2020-05-30 15:18:30.254‘) -> Invalid Date;
  • 咨询公司里的前端大佬,发现他们前端都用Moment.js做日期转换;
  • 为什么Moment.js能够实现任意日期字符串格式转换呢? 先上结论:底层使用new Date(年,月,日,时,分,秒,毫秒)函数,这个函数基本上所有浏览器都实现了。

注:Moment.js很重(源码为4600行左右),所以有很多替代方案的,如:Dayjs、miment等,甚至根据浏览器的兼容情况自行写个轻量级的库也是可行的。

2 使用




Learn MomentJs

3 源码跟踪

  • 1 初始化moment():返回createLocal函数;
  • 2 初始化配置类:调用createLocal函数 -> createLocalOrUTC函数 ;
  • 3 完善配置信息并校验:prepareConfig函数 -> configFromStringAndFormat函数 -> configFromArray函数 -> checkOverflow函数;
  • 4 根据配置信息创建Moment对象:Moment构造函数。
;(function (global, factory) {
    typeof exports === ‘object‘ && typeof module !== ‘undefined‘ ? module.exports = factory() :
    typeof define === ‘function‘ && define.amd ? define(factory) :
    global.moment = factory()
}(this, (function () { ‘use strict‘;
    var hookCallback;

    function hooks () {
        return hookCallback.apply(null, arguments);
    }

    function setHookCallback (callback) {
        hookCallback = callback;
    }
     
    // 2 初始化配置类                  
    // 2.1 ex: input="2020-05-30 14:08:35",format="YYYY-MM-DD HH:mm:ss",locale=null,strict=null                 
    function createLocal (input, format, locale, strict) {
        return createLocalOrUTC(input, format, locale, strict, false);
    }
                      
    // 2.2 创建Local或者UTC Moment对象
	function createLocalOrUTC (input, format, locale, strict, isUTC) {
        var c = {};
		// 检验input字符串
        if ((isObject(input) && isObjectEmpty(input)) ||
                (isArray(input) && input.length === 0)) {
            input = undefined;
        }
        // 配置初始化
        c._useUTC = c._isUTC = isUTC;
        c._l = locale;
        c._i = input;
        c._f = format;
        c._strict = strict;
        return createFromConfig(c);
    }
    
    // 4 通过配置类创建Moment对象                  
    function createFromConfig (config) {
        var res = new Moment(checkOverflow(prepareConfig(config)));
        if (res._nextDay) {
            res.add(1, ‘d‘);
            res._nextDay = undefined;
        }
        return res;
    }
                      
    // 3 完善配置信息
    function prepareConfig (config) {
        var input = config._i,
            format = config._f;
        config._locale = config._locale || getLocale(config._l);
        if (input === null || (format === undefined && input === ‘‘)) {
            return createInvalid({nullInput: true});
        }
        if (typeof input === ‘string‘) {
            config._i = input = config._locale.preparse(input);
        }
        // 支持Moment对象、日期对象、数组对象、字符串格式、配置对象格式
        if (isMoment(input)) {
            return new Moment(checkOverflow(input));
        } else if (isDate(input)) {
            config._d = input;
        } else if (isArray(format)) {
            configFromStringAndArray(config);
        } else if (format) {
            // 以此为例
            configFromStringAndFormat(config);
        }  else {
            configFromInput(config);
        }
        if (!isValid(config)) {
            config._d = null; // _d为日期对象,下面讲解。
        }
        return config;
    }
                      
    // 3.1 通过字符串模板创建Moment对象中的日期对象(_d)
    function configFromStringAndFormat(config) {
        // 创建Date对象用的数组,如:[年,月,日,时,分,秒]
        config._a = [];
        getParsingFlags(config).empty = true;
        var string = ‘‘ + config._i,
            i, parsedInput, tokens, token, skipped,
            stringLength = string.length,
            totalParsedInputLength = 0;
        // tokens=[‘YYYY‘,‘-‘,‘MM‘,‘-‘,‘DD‘,‘ ‘,‘HH‘,‘:‘,‘mm‘,‘:‘,‘ss‘]
        tokens = expandFormat(config._f, config._locale).match(formattingTokens) || [];

        for (i = 0; i  0) {
                    getParsingFlags(config).unusedInput.push(skipped);
                }
                string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
                totalParsedInputLength += parsedInput.length;
            }
            
            if (formatTokenFunctions[token]) {
                if (parsedInput) {
                    getParsingFlags(config).empty = false;
                }
                else {
                    getParsingFlags(config).unusedTokens.push(token);
                }
                // 将parsedInput添加到config._a数组中
                addTimeToArrayFromToken(token, parsedInput, config);
            }
            else if (config._strict && !parsedInput) {
                getParsingFlags(config).unusedTokens.push(token);
            }
        }

        // add remaining unparsed input length to the string
        getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength;
        if (string.length > 0) {
            getParsingFlags(config).unusedInput.push(string);
        }

        // clear _12h flag if hour is  0) {
            getParsingFlags(config).bigHour = undefined;
        }

        getParsingFlags(config).parsedDateParts = config._a.slice(0);
        getParsingFlags(config).meridiem = config._meridiem;
        // handle meridiem
        config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem);

        configFromArray(config);
        checkOverflow(config);
    }
                      
    // 3.2 当config._a日期相关数组完善后                  
    function configFromArray (config) {
        var i, date, input = [], currentDate, expectedWeekday, yearToUse;
        
        // 年月日
        currentDate = currentDateArray(config);

        // ...
        
        // 简单描述:将config._a数组中的元素暂存至input数组中用于调用createDate方法。
        
        // Default to current date.
        // * if no year, month, day of month are given, default to today
        // * if day of month is given, default month and year
        // * if month is given, default only year
        // * if year is given, don‘t default anything
        for (i = 0; i = 0) {
            date = new Date(y + 400, m, d, h, M, s, ms);
            if (isFinite(date.getFullYear())) {
                date.setFullYear(y);
            }
        } else {
            // 最终调用通用的日期创建方法(这个方法所有浏览器都实现了)
            date = new Date(y, m, d, h, M, s, ms);
        }
        return date;
    }
          
    // 4 创建Moment对象                  
    function Moment(config) {
        copyConfig(this, config);
        this._d = new Date(config._d != null ? config._d.getTime() : NaN);
        if (!this.isValid()) {
            this._d = new Date(NaN);
        }
        // Prevent infinite loop in case updateOffset creates new moment
        // objects.
        if (updateInProgress === false) {
            updateInProgress = true;
            hooks.updateOffset(this);
            updateInProgress = false;
        }
    }

                      
    // 中间省略亿点细节
                      
    hooks.version = ‘2.24.0‘;

    // 设置hooks为createLocal                  
    setHookCallback(createLocal);

    // currently HTML5 input type only supports 24-hour formats
    hooks.HTML5_FMT = {
        DATETIME_LOCAL: ‘YYYY-MM-DDTHH:mm‘,             // 
        DATETIME_LOCAL_SECONDS: ‘YYYY-MM-DDTHH:mm:ss‘,  // 
        DATETIME_LOCAL_MS: ‘YYYY-MM-DDTHH:mm:ss.SSS‘,   // 
        DATE: ‘YYYY-MM-DD‘,                             // 
        TIME: ‘HH:mm‘,                                  // 
        TIME_SECONDS: ‘HH:mm:ss‘,                       // 
        TIME_MS: ‘HH:mm:ss.SSS‘,                        // 
        WEEK: ‘GGGG-[W]WW‘,                             // 
        MONTH: ‘YYYY-MM‘                                // 
    };

    // 1. 返回hooks,实际返回createLocal函数
    return hooks;
})));

3 参考

ECMAScript? Language Specification

invalid date in safari

Moment.js:moment(string,format)源码解析

标签:har   轻量级   port   token   maps   fse   actor   and   const   

原文地址:https://www.cnblogs.com/linzhanfly/p/12993641.html


评论


亲,登录后才可以留言!