本文详细讲解如何在JavaScript中封装一个支持自定义模板的当前时间格式化函数。通过占位符设计、时间分量提取、补零处理以及正则单次查表替换,轻松实现YYYY-MM-DD HH:mm:ss等任意格式的时间输出。
JavaScript怎么封装支持自定义模板的当前时间格式化函数
时间:2026-11-12
浏览:202
文章目录
Date 对象虽然提供了获取年、月、日、时、分、秒的基础接口,但没有内置类似其他语言的模板格式化工具。要封装一个支持自定义模板(如 YYYY-MM-DD HH:mm:ss)的当前时间格式化函数,最稳健的做法是:提取当前时间各分量并完成格式补零,构建占位符映射字典,最后通过全局正则表达式执行单次查表替换。
一、占位符规范与核心设计
设计自定义模板函数的首要步骤是制定统一、清晰的占位符规范。占位符既要符合主流使用习惯(如国际标准与社区开源库标准),又要能区分“是否自动补齐前导零”以及“24小时制与12小时制”。
下表列出了本函数支持的标准占位符定义及其映射规则:
| 占位符 | 代表含义 | 取值示例 | 补充说明 |
|---|---|---|---|
YYYY / YY |
完整四位年份 / 两位缩写年份 | 2026 / 26 | 取自 getFullYear() |
MM / M |
两位月份(补零) / 一位月份 | 03 或 11 / 3 或 11 | getMonth() + 1,需注意原生月份从 0 开始 |
DD / D |
两位日期(补零) / 一位日期 | 05 或 28 / 5 或 28 | 取自 getDate() |
HH / H |
两位 24 小时制(补零) / 一位小时 | 08 或 23 / 8 或 23 | 取自 getHours(),范围 0-23 |
hh / h |
两位 12 小时制(补零) / 一位小时 | 08 或 11 / 8 或 11 | 取模换算,范围 1-12 |
mm / m |
两位分钟(补零) / 一位分钟 | 06 或 45 / 6 或 45 | 取自 getMinutes(),小写区分月份 MM |
ss / s |
两位秒数(补零) / 一位秒数 | 09 或 58 / 9 或 58 | 取自 getSeconds() |
SSS |
三位毫秒(补零) | 042 或 890 | 取自 getMilliseconds() |
二、核心实现步骤剖析
实现该函数主要分为四个步骤:
-
实例化当前时间:通过
new Date()初始化时间对象。为保证函数的通用性,还可以允许调用方选择性传入已有Date对象或时间戳,若不传则默认取当前时间。 -
提取时间分量并对齐:调用原生方法提取分量。需要格外注意
getMonth()返回值是0 ~ 11,在日常展示时必须加上1;24小时与12小时制的换算可通过hours % 12 || 12实现。 -
构建占位符字典:将提取出的分量以字符串形式存入对象中,其中
MM、DD、HH、mm、ss等长标记使用String.prototype.padStart(2, '0')填充为两位。 -
正则一次性查表替换:使用统一的正则表达式同时匹配所有标记,在
replace回调中查表替换。这一步至关重要,能彻底避免链式替换造成的二次污染。
三、为什么必须使用“正则单次查表替换”
初学者在封装格式化函数时,经常容易写出类似下面的错误模式:
// 错误示例:连续链式替换(存在二次替换污染风险)
template
.replace('YYYY', year)
.replace('MM', month)
.replace('DD', day)
.replace('HH', hour)
.replace('mm', minute)
.replace('ss', second);
这种链式 replace 方式有严重隐患:前面的替换结果可能会引入后续占位符相同的字母。例如,若年份替换为了 2026 没什么问题,但如果是处理自定义格式或者含其他字母的模板时,前面的替换结果一旦碰巧包含了后续占位符(如 M、D、s 等),就会导致原本已经替换完成的内容被再次篡改。
正规解法是利用 全局正则表达式分支匹配(/YYYY|MM|.../g)并结合回调函数查表。因为 JavaScript 的正则引擎会对目标字符串进行单向线性扫描,已经被替换的文本不会被再次扫入正则匹配队列,从而确保原子性与绝对安全。
四、完整封装代码实现
下面给出生产可用的完整封装代码。该实现默认格式化当前时间(new Date()),默认模板为 "YYYY-MM-DD HH:mm:ss",同时支持传入自定义模板以及特定时间对象:
/**
* 格式化时间函数
* @param {string} [template='YYYY-MM-DD HH:mm:ss'] - 目标格式模板
* @param {Date|number|string} [targetDate=new Date()] - 目标时间,默认当前时间
* @returns {string} 格式化后的时间字符串
*/
function formatTime(template = 'YYYY-MM-DD HH:mm:ss', targetDate = new Date()) {
// 1. 规范化 Date 实例
const date = targetDate instanceof Date ? targetDate : new Date(targetDate);
// 2. 检查日期有效性
if (Number.isNaN(date.getTime())) {
throw new TypeError('formatTime: 提供的时间参数无效');
}
// 3. 提取各个时间分量
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const hours24 = date.getHours();
const hours12 = hours24 % 12 || 12;
const minutes = date.getMinutes();
const seconds = date.getSeconds();
const milliseconds = date.getMilliseconds();
// 4. 构建占位符映射字典
const tokens = {
YYYY: String(year),
YY: String(year).slice(-2),
MM: String(month).padStart(2, '0'),
M: String(month),
DD: String(day).padStart(2, '0'),
D: String(day),
HH: String(hours24).padStart(2, '0'),
H: String(hours24),
hh: String(hours12).padStart(2, '0'),
h: String(hours12),
mm: String(minutes).padStart(2, '0'),
m: String(minutes),
ss: String(seconds).padStart(2, '0'),
s: String(seconds),
SSS: String(milliseconds).padStart(3, '0')
};
// 5. 按照“长标记优先”的原则构造全局匹配正则
// 避免 MM 被先行误匹配为 M,或者 YYYY 被误匹配为 YY
const tokenRegex = /YYYY|SSS|YY|MM|DD|HH|hh|mm|ss|M|D|H|h|m|s/g;
// 6. 查表替换并返回
return template.replace(tokenRegex, (match) => tokens[match]);
}
/**
* 专用于当前时间的快捷调用函数
* @param {string} [template='YYYY-MM-DD HH:mm:ss'] - 目标格式模板
* @returns {string} 当前时间的格式化字符串
*/
function formatNow(template = 'YYYY-MM-DD HH:mm:ss') {
return formatTime(template, new Date());
}
五、实际使用与场景验证
完成封装后,可以在各种常见的业务场景下直接调用 formatNow()。以下给出不同模板下的运行效果示例:
// 场景 1:默认标准完整时间(未传参)
console.log(formatNow());
// 输出示例:"2026-11-11 01:50:55"
// 场景 2:仅获取当前年月日(常用于报表归档、文件命名)
console.log(formatNow('YYYY-MM-DD'));
// 输出示例:"2026-11-11"
// 场景 3:中文语义化日期(常用于页面顶部抬头展示)
console.log(formatNow('YYYY年MM月DD日 HH时mm分ss秒'));
// 输出示例:"2026年11月11日 01时50分55秒"
// 场景 4:紧凑型时间戳(常用于订单流水号前缀)
console.log(formatNow('YYYYMMDDHHmmss'));
// 输出示例:"20261111015055"
// 场景 5:包含高精度毫秒(常用于系统日志追踪)
console.log(formatNow('HH:mm:ss.SSS'));
// 输出示例:"01:50:55.042"
// 场景 6:不补零的极简展示与12小时制
console.log(formatNow('M月D日 上午/下午 hh:mm'));
// 输出示例:"11月11日 上午/下午 01:50"
六、细节提醒与进阶拓展
- 大小写敏感性:月份必须大写
MM,分钟必须小写mm。如果两者混淆,会导致日期与时间解析紊乱。 - 正则优先顺序:在定义正则的分支条件时,必须将多字符标记(如
YYYY、MM)写在单字符标记(如YY、M)前面,否则正则匹配到前面的短标记就会提前截断。 - 时区一致性:
getFullYear()、getHours()获取的是浏览器或宿主机器本地时区时间。如果业务需要固定为 UTC 时间,只需在提取时将方法替换为getUTCFullYear()、getUTCHours()等。
七、总结
封装一个支持自定义模板的时间格式化函数并不复杂,核心在于不引入过重第三方库的前提下,保证代码的准确性与防御性。通过规范化的占位符命名、原生的 padStart 补零以及基于“长标记优先”的正则单次替换,开发者只需几十行轻量代码就能满足绝大多数前端与服务端关于时间展示的格式化诉求。
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。