zoukankan      html  css  js  c++  java
  • Moment.js:moment(string,format)源码解析

    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 使用

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <script src="http://cdn.staticfile.org/moment.js/2.24.0/moment.js"></script>
        <title>Learn MomentJs</title>
    </head>
    <body>
        <script>
            var moment1 = moment("2020-05-30 14:08:35","YYYY-MM-DD HH:mm:ss");
            var date = moment1._d;
            console.log(date);
        </script>
    </body>
    </html>
    

    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 < tokens.length; i++) {
                token = tokens[i]; // 首次为YYYY
                parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; // 首次为2020
                if (parsedInput) {
                    skipped = string.substr(0, string.indexOf(parsedInput));
                    if (skipped.length > 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 <= 12
            if (config._a[HOUR] <= 12 &&
                getParsingFlags(config).bigHour === true &&
                config._a[HOUR] > 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 < 3 && config._a[i] == null; ++i) {
                config._a[i] = input[i] = currentDate[i];
            }
    
            // Zero out whatever was not defaulted, including time
            for (; i < 7; i++) {
                config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
            }
    
            // Check for 24:00:00.000
            if (config._a[HOUR] === 24 &&
                    config._a[MINUTE] === 0 &&
                    config._a[SECOND] === 0 &&
                    config._a[MILLISECOND] === 0) {
                config._nextDay = true;
                config._a[HOUR] = 0;
            }
    
            // 实际创建日期的方法,前面已经把
            config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input);
            expectedWeekday = config._useUTC ? config._d.getUTCDay() : config._d.getDay();
    
            if (config._tzm != null) {
                config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
            }
    
            if (config._nextDay) {
                config._a[HOUR] = 24;
            }
    
            if (config._w && typeof config._w.d !== 'undefined' && config._w.d !== expectedWeekday) {
                getParsingFlags(config).weekdayMismatch = true;
            }
        }
                          
        // ex: y=2020,m=4,d=30,h=14,M=8,s=35,ms=0                  
        function createDate (y, m, d, h, M, s, ms) {
            var date;
            // the date constructor remaps years 0-99 to 1900-1999
            if (y < 100 && y >= 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',             // <input type="datetime-local" />
            DATETIME_LOCAL_SECONDS: 'YYYY-MM-DDTHH:mm:ss',  // <input type="datetime-local" step="1" />
            DATETIME_LOCAL_MS: 'YYYY-MM-DDTHH:mm:ss.SSS',   // <input type="datetime-local" step="0.001" />
            DATE: 'YYYY-MM-DD',                             // <input type="date" />
            TIME: 'HH:mm',                                  // <input type="time" />
            TIME_SECONDS: 'HH:mm:ss',                       // <input type="time" step="1" />
            TIME_MS: 'HH:mm:ss.SSS',                        // <input type="time" step="0.001" />
            WEEK: 'GGGG-[W]WW',                             // <input type="week" />
            MONTH: 'YYYY-MM'                                // <input type="month" />
        };
    
        // 1. 返回hooks,实际返回createLocal函数
        return hooks;
    })));
    

    3 参考

    ECMAScript® Language Specification

    invalid date in safari

  • 相关阅读:
    arm-linux-gcc4.4.3编译busybox-1.25.0
    arm-linux-gcc4.4.3编译s3c2410平台linux内核
    Ubuntu 16.04上编译SkyEye的测试程序
    Ubuntu16.04上安装arm-linux-gcc4.4.3
    Ubuntu下安装deb包命令
    基环树DP
    整理
    无穷的远方,无数的人们,都和我有关
    死亡之前,我是生活本身
    我是sb
  • 原文地址:https://www.cnblogs.com/linzhanfly/p/12993641.html
Copyright © 2011-2022 走看看