JavaScript常用功能实例:表单验证、数组处理与日期格式化
本文结合具体开发场景,详细讲解前端日常开发中使用频率最高的三大核心技能:表单数据校验与状态反馈、现代数组声明式链式处理、以及原生与国际化日期格式化实战方案。
假设你正在开发一个用户管理后台或订单提报页面:用户在表单中填写手机号、邮箱和金额;前端需要在提交前完成合规性检查;接着对返回的订单集合进行过滤、统计与结构重组;最后将时间戳格式化为直观易读的本地时间。这三项操作构成了绝大多数中前台应用的数据基石。掌握清晰、规范且无副作用的实现方式,不仅能减少重复造轮子,更能显著降低线上隐蔽异常的发生概率。
一、实战表单验证:从即时反馈到安全拦截
表单验证的核心目标是:向用户清晰指出输入不符合预期的具体原因,并在全部内容校验通过前拦截无意义的网络请求。在原生 JavaScript 中,常见的做法是将校验规则从 UI 渲染中解耦出来,形成可复用的规则映射。
1. 常用验证规则策略对象
我们可以定义一个纯粹的验证策略集合,每个函数接收字段值并返回一个布尔值:
const validatorRules = {
// 必填项非空校验(去除前后空白字符)
required(value) {
return value !== null && value !== undefined && String(value).trim().length > 0;
},
// 邮箱格式验证(常规标准匹配)
isEmail(value) {
const emailPattern = /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/;
return emailPattern.test(String(value).trim());
},
// 中国大陆手机号格式验证(11位数字,以1开头)
isPhone(value) {
const phonePattern = /^1[3-9]\d{9}$/;
return phonePattern.test(String(value).trim());
},
// 密码强度验证(8-20位,必须包含字母与数字)
isPassword(value) {
const pwdPattern = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d!@#$%^&*._~-]{8,20}$/;
return pwdPattern.test(String(value));
}
};
2. 统一验证执行器与 DOM 联动
在用户交互时,常见的验证触发点有两个:失焦(blur)时检查单个输入框并显示提示,点击提交按钮(submit)时做整体遍历检查。如果存在未通过项,立即通过 event.preventDefault() 阻止表单提交。
// 验证单一字段并在页面显示错误
function validateField(inputElement, ruleName, errorMessage) {
const value = inputElement.value;
const isValid = validatorRules[ruleName](value);
const errorContainer = document.querySelector(`#${inputElement.id}-error`);
if (!isValid) {
inputElement.classList.add('input-error');
if (errorContainer) {
errorContainer.textContent = errorMessage;
errorContainer.style.display = 'block';
}
return false;
} else {
inputElement.classList.remove('input-error');
if (errorContainer) {
errorContainer.textContent = '';
errorContainer.style.display = 'none';
}
return true;
}
}
// 绑定表单提交事件
const userForm = document.querySelector('#registrationForm');
if (userForm) {
userForm.addEventListener('submit', (event) => {
const emailInput = document.querySelector('#email');
const phoneInput = document.querySelector('#phone');
const pwdInput = document.querySelector('#password');
const isEmailValid = validateField(emailInput, 'isEmail', '请输入合法的电子邮箱地址');
const isPhoneValid = validateField(phoneInput, 'isPhone', '请输入正确的11位手机号码');
const isPwdValid = validateField(pwdInput, 'isPassword', '密码须包含字母和数字,长度8-20位');
if (!isEmailValid || !isPhoneValid || !isPwdValid) {
event.preventDefault(); // 关键步骤:阻断提交
return;
}
// 校验全数通过,后续可组装数据发起异步请求
});
}
在对输入框重复调用正则时,切勿在判断函数中使用带有 /g 标志的正则表达式(例如 /\d+/g)。带有 g 的 RegExp 实例内部维护 lastIndex 属性,连续多次执行 .test() 会轮流返回 true 和 false,导致校验结果时对时错。单字段校验推荐使用严格的起止锚点 ^...$ 且不加 g 标志。
二、高效数组处理:不可变数据流与实用管线
前端从服务端拉取的数据往往是平铺或嵌套的对象数组。现代 JavaScript 提倡“纯函数”和“不可变性”(Immutability),即在清洗与重构数据时,不改变原始输入数组,而是返回崭新的计算结果。
1. 链式操作组合:过滤、映射与聚合
考虑一份真实的电商订单明细列表,我们希望筛选出已支付的订单、计算折后实付价,并最终汇总出总收益:
const orders = [
{ id: 101, user: '张三', amount: 120, status: 'PAID', discount: 0.9 },
{ id: 102, user: '李四', amount: 280, status: 'PENDING', discount: 1.0 },
{ id: 103, user: '王五', amount: 450, status: 'PAID', discount: 0.8 },
{ id: 104, user: '赵六', amount: 80, status: 'CANCELLED', discount: 1.0 }
];
// 1. 过滤:只保留已支付订单
// 2. 映射:计算折后实际到账金额并提取必要展示字段
// 3. 聚合:计算当前批次的总到账额度
const processedSummary = orders
.filter(order => order.status === 'PAID')
.map(order => ({
orderId: order.id,
customer: order.user,
finalPrice: Number((order.amount * order.discount).toFixed(2))
}));
const totalRevenue = processedSummary.reduce((accumulator, item) => {
return accumulator + item.finalPrice;
}, 0); // 务必提供明确的初始值 0
console.log(processedSummary);
// 示例输出:
// [
// { orderId: 101, customer: '张三', finalPrice: 108 },
// { orderId: 103, customer: '王五', finalPrice: 360 }
// ]
console.log('总营收:', totalRevenue); // 示例输出: 总营收: 468
2. 数组去重与对象分组
除了常规的链式计算,根据主键去重以及按指定属性分组也是常见需求:
// 基础类型数组去重:使用 Set
const rawTags = ['JavaScript', 'CSS', 'HTML', 'JavaScript', 'React', 'CSS'];
const uniqueTags = [...new Set(rawTags)];
// 结果: ['JavaScript', 'CSS', 'HTML', 'React']
// 对象数组依据唯一属性去重:使用 Map
function dedupByProperty(array, key) {
const map = new Map();
return array.filter(item => !map.has(item[key]) && map.set(item[key], true));
}
// 对象数组分组:利用 reduce 实现全兼容分组
function groupBy(array, keySelector) {
return array.reduce((groups, item) => {
const key = keySelector(item);
if (!groups[key]) {
groups[key] = [];
}
groups[key].push(item);
return groups;
}, {});
}
sort()、reverse() 和 splice() 是 JavaScript 数组中典型的“原地修改方法”(In-place Mutators),会直接污染原始数组。在 Vue、React 等现代框架中,直接修改原始数据往往导致不可预测的渲染漏洞。如果需要排序或翻转,先创建浅拷贝:const sorted = [...original].sort(...),或使用较新环境支持的 toSorted() 方法。
三、日期与时间格式化:原生封装与国际化 API
处理日期时,后端通常返回两种标准格式:13位或10位毫秒/秒级时间戳(如 1730000000000),或是 ISO 8601 标准字符串(如 "2026-10-25T18:02:48Z")。前端最常遇到的两个坑是:原生 Date.prototype.getMonth() 范围是 0 到 11;未正确补全前导零(导致输出 2026-5-3 而非标准 2026-05-03)。
1. 轻量通用格式化函数(零外部依赖)
利用现代字符串原生方法 String.prototype.padStart(),可以编写出一个清晰、可靠且可自定义模板的格式化工具:
function formatDate(dateInput, formatPattern = 'YYYY-MM-DD HH:mm:ss') {
if (!dateInput) return '';
// 兼容时间戳字符串与数字,处理秒级时间戳(10位)自动扩充至毫秒
let timestamp = dateInput;
if (typeof dateInput === 'string' && !isNaN(Number(dateInput))) {
timestamp = Number(dateInput);
}
if (typeof timestamp === 'number' && String(timestamp).length === 10) {
timestamp = timestamp * 1000;
}
const date = new Date(timestamp);
// 严格检查日期对象是否有效
if (isNaN(date.getTime())) {
console.warn('[formatDate] 传入了无效的日期参数:', dateInput);
return '';
}
const tokens = {
YYYY: date.getFullYear(),
MM: String(date.getMonth() + 1).padStart(2, '0'), // 月份需自增 1
DD: String(date.getDate()).padStart(2, '0'),
HH: String(date.getHours()).padStart(2, '0'),
mm: String(date.getMinutes()).padStart(2, '0'),
ss: String(date.getSeconds()).padStart(2, '0')
};
return formatPattern.replace(/YYYY|MM|DD|HH|mm|ss/g, matched => tokens[matched]);
}
// 使用示例
console.log(formatDate(new Date()));
// 输出示例: 2026-10-25 18:02:48
console.log(formatDate('2026-10-25T10:02:48.000Z', 'YYYY年MM月DD日 HH:mm'));
// 输出示例: 2026年10月25日 18:02 (按本地时区渲染)
2. 现代国际化 API:Intl.DateTimeFormat
ECMAScript 国际化标准提供了原生且高效的 Intl.DateTimeFormat。它原生支持指定时区和不同语言环境,无需安装任何体积庞大的第三方日期库:
// 创建一个针对中文语境的格式化器
const chineseDateFormatter = new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false, // 采用24小时制
timeZone: 'Asia/Shanghai'
});
console.log(chineseDateFormatter.format(new Date()));
// 输出示例: 2026/10/25 18:02:48
// 友好的人性化相对时间计算(刚刚、5分钟前)
function getRelativeTimeString(timestamp) {
const diff = Date.now() - new Date(timestamp).getTime();
const seconds = Math.floor(diff / 1000);
if (seconds < 60) return '刚刚';
const minutes = Math.floor(seconds / 60);
if (minutes < 60) return `${minutes} 分钟前`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `${hours} 小时前`;
const days = Math.floor(hours / 24);
return `${days} 天前`;
}
四、三项功能对照与开发自查表
为了在实际编码过程中快速核验关键逻辑,下表整理了三项高频功能的关键 API、易错点与推荐实践:
| 功能模块 | 核心 API / 方法 | 常见隐患 / 易错点 | 推荐规范 / 最佳实践 |
|---|---|---|---|
| 表单验证 | RegExp.test(), event.preventDefault() |
带全局 /g 正则导致的 lastIndex 状态漂移;缺少 trim() 过滤空白 |
使用策略模式抽取纯校验函数;严格使用 ^...$;在提交事件顶层集中执行判断 |
| 数组处理 | filter(), map(), reduce(), Set |
sort() 原地改变原数组;reduce() 遗漏初始值导致空数组抛出异常 |
保持数据流不可变性;必要时用解构 [...arr] 浅拷贝;为 reduce 提供确切初始值 |
| 日期格式化 | padStart(), Intl.DateTimeFormat |
月份未加 1;10位秒级与13位毫秒级时间戳混淆;跨浏览器日期字符串解析不一致 | 入参统一校验 isNaN(date.getTime());格式化统一补前导零;优先使用 Intl 处理时区 |
在日常开发中,前端代码的质量往往体现在对这些高频小事的处理细致度上。通过构建结构清晰的表单验证规则、善用不可变式的数组链式处理、以及规范严谨的日期补零与时区解析,即可在不依赖外部大型依赖包的前提下,写出健壮、可维护且运行高效的前端代码。
JavaScript异步发送HTTP请求时怎么优雅地处理错误
2026-11-05 17:10:44
5款主流JS在线运行工具对比:CodePen、JSFiddle与StackBlitz怎么选
2026-11-05 17:04:04
JavaScript入门书籍避坑:这些书不适合初学者
2026-11-05 16:31:15
JS使用Math.round实现保留两位小数的原理与代码
2026-11-05 15:35:55
JavaScript 原生 Date 对象怎么实现日期加减
2026-11-05 14:30:00






![JS创建二维数组:为什么Array(n).fill([])会导致数据联动](https://img.javascript.cn/tit/articles/13236/20261102/f618c991891ff37efb12df6af2ce0ccb0da8779a907fe1d2f7275d60c84fdd77.webp)

