Moment.js:moment(string,format)源码解析
Posted 月下小魔王
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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
以上是关于Moment.js:moment(string,format)源码解析的主要内容,如果未能解决你的问题,请参考以下文章
Moment.js轻量级的JavaScript时间库-常见用法汇总