第 8 节:表单 — 前端校验的第一道防线

第 8 节:表单 --- 前端校验的第一道防线

在前两节中,我们已经能让页面响应点击、输入等事件,并动态更新 DOM。但要构建一个完整的交互系统(比如学生信息录入),还缺少关键一环:收集用户输入的数据,并确保它符合规则。表单就是这个环节的主角,而前端校验则是它的第一道防线。

本节将结合第 6 节(DOM 操作)和第 7 节(事件)的知识,建立一套可复用的表单校验模式。


1. 为什么需要前端校验

后端校验是不可替代的最终防线,但前端校验的价值在于:

  • 即时反馈:用户敲完字段就能看到对错,无需等待网络往返。
  • 减少无效请求:把明显不合格的数据挡在浏览器内,减轻后端压力。
  • 引导用户:清晰的提示能帮助用户更快完成表单。

作为后端开发者,你早已习惯用注解(@NotBlank@Pattern@Range)验证数据。前端校验不过是将这套规则从服务端迁移到客户端,并让它在用户输入的那一刻起作用。

数据流向

用户输入 → 前端实时/提交校验 → 通过后发送请求 → 后端二次校验 → 入库


2. 取出表单数据:FormData 与手动取值

表单校验的第一步是把用户填写的数据读出来。JS 提供了两种主要方式。

2.1 手动取值:按 ID 逐个读取

javascript 复制代码
const data = {
  studentId: document.getElementById('studentId').value,  // "2024001"
  name:      document.getElementById('name').value,
  age:       document.getElementById('age').value,        // "20"  注意:永远是字符串!
  // 复选框需要单独处理
  hobby: [...document.querySelectorAll('input[name="hobby"]:checked')].map(el => el.value)
};

注意点

  • .value 始终返回字符串(即使输入的是数字类型,如年龄)。
  • 复选框(checkbox)、多选列表等需特殊处理,不能直接 .value
  • 适合只取个别字段或需要加工值的场景。

2.2 FormData:一次性收集整个表单

javascript 复制代码
const form = document.getElementById('studentForm');
const fd = new FormData(form);

fd.get('name');             // "张三"
fd.getAll('hobby');         // ["篮球", "音乐"]  ← 多值字段必须用 getAll
Object.fromEntries(fd);     // 转换为普通对象 { name: "张三", ... }

FormData 的规则

  • 控件必须有 name 属性,否则会被忽略(就像菜品没有名字,系统不认)。
  • 多值字段(复选框、多选列表)用 getAll() 获取所有值,fromEntries 只会保留最后一个。
  • 未选中的复选框不会出现在 FormData 中。

选择建议

场景 方式
整体提交(字段完整) FormData,一行搞定
只取 1~2 个字段,或需要数字转换 手动取值
包含文件上传 FormData 自动支持 File 对象

后端对照new FormData(form) 类似 Spring MVC 的 @ModelAttribute 自动绑定------表单控件名(name)对应 DTO 属性名。


3. 实时校验:input 事件 + 正则表达式

3.1 实时反馈的原理

第 7 节讲过,input 事件在输入框内容每次变化时都会触发。这为"边输入边检查"提供了引擎。

javascript 复制代码
const idInput = document.getElementById('studentId');
const ID_PATTERN = /^\d{7}$/;   // 7 位纯数字

idInput.addEventListener('input', (e) => {
  const value = e.target.value.trim();
  if (value === '') {
    // 空值状态
  } else if (ID_PATTERN.test(value)) {
    // 格式正确
  } else {
    // 格式错误
  }
});

3.2 JS 正则:够用的基础知识

与后端正则类似,但 JS 中正则通常用字面量 写法(/pattern/),核心方法为 .test(str)

javascript 复制代码
// 常用正则示例
/^\d{7}$/                // 学号:恰好 7 位数字
/^1[3-9]\d{9}$/         // 手机号:1 开头,第二位 3~9,后 9 位数字
.trim() !== ''           // 非空判断(去掉前后空格)

// test() 返回布尔值
/^\d{7}$/.test('2024001');   // true
/^\d{7}$/.test('123');       // false

必须写 ^$/^\d{7}/ 只能保证开头 有 7 位数字,无法阻止 12345678 通过;加上 $ 才强制整个字符串匹配。

3.3 input vs change:选择哪个事件?

事件 触发时机 适用场景
input 每次键击(实时) 需要边输入边提示,如注册、搜索
change 内容改变后失焦 下拉框切换、复选框勾选、整体选择完毕

口诀input 是边打边报,change 是选完才报。

实时校验核心字段(如学号、手机号)使用 input;下拉框、单选框等使用 change


4. 三种校验模式:什么时候验证?

并非所有字段都适合每敲一个字就检查,我们可以根据场景组合三种模式。

4.1 三种时机

模式 事件 优点 缺点 适用场景
实时校验 input 反馈最快,体验最好 频繁触发,可能需要防抖 关键字段(用户名、密码、手机)
失焦校验 blur 不打断输入,一次完整输入后检查 反馈略慢 长表单、复杂规则字段(如身份证)
提交校验 submit 逻辑集中,一次性检查所有字段 用户可能填很多才提示 可作为最终兜底,配合前两种模式

4.2 组合策略(实际项目推荐)

  • 核心字段 :实时校验(input),给即时指引。
  • 次要字段 :失焦校验(blur),减少干扰。
  • 全局把关:提交时再跑一次全量验证,防止漏网之鱼(例如用户直接跳过某些输入框点提交)。

三种模式可以调用同一个校验函数,避免逻辑重复。


5. 设计可复用的校验函数(表驱动)

5.1 坏味道:散落的 if-else

如果每个字段、每种事件里都写一段判断,代码会迅速膨胀,且新增字段要改多处。

5.2 好模式:规则表 + 统一校验器

仿照后端 DTO 注解,我们将所有校验规则抽取到一个规则表对象中,然后用一个函数遍历执行。

① 定义规则表(RULES)

javascript 复制代码
const RULES = {
  studentId: {
    label: '学号', required: true,
    pattern: /^\d{7}$/, message: '学号必须为 7 位纯数字'
  },
  name: {
    label: '姓名', required: true, message: '姓名不能为空'
  },
  age: {
    label: '年龄', required: true,
    validator(value) {                      // 自定义校验函数
      const num = Number(value);
      if (!Number.isInteger(num)) return '年龄必须是整数';
      if (num < 1 || num > 100) return '年龄必须在 1~100 之间';
      return '';                            // 返回空串表示通过
    }
  },
  phone: {
    label: '手机号', required: false,       // 选填
    pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确'
  },
};

每条规则可包含:

  • required: 是否必填(对应 @NotBlank
  • pattern: 正则格式(对应 @Pattern
  • validator(value): 任意自定义逻辑(对应 @Min@Max 等)

② 统一校验函数 validateField

javascript 复制代码
function validateField(fieldName) {
  const input = document.getElementById(fieldName);
  const rule = RULES[fieldName];
  const value = input.value.trim();

  // 1. 必填检查
  if (rule.required && value === '') {
    return rule.label + '不能为空';
  }
  // 2. 正则检查(仅当有值和正则规则时)
  if (rule.pattern && value !== '' && !rule.pattern.test(value)) {
    return rule.message;
  }
  // 3. 自定义检查
  if (rule.validator && value !== '') {
    return rule.validator(value);
  }
  return '';   // 无错误
}

③ 显示/清除错误 (结合第 6 节的 classList 知识)

javascript 复制代码
function showError(fieldName, message) {
  const input = document.getElementById(fieldName);
  input.classList.add('invalid');
  document.getElementById(fieldName + '-error').textContent = message;
}
function clearError(fieldName) {
  document.getElementById(fieldName).classList.remove('invalid');
  document.getElementById(fieldName + '-error').textContent = '';
}

④ 绑定事件:一套代码覆盖所有字段和时机

javascript 复制代码
Object.keys(RULES).forEach(fieldName => {
  const input = document.getElementById(fieldName);
  input.addEventListener('input', () => handleField(fieldName));   // 实时
  input.addEventListener('blur',  () => handleField(fieldName));   // 失焦
});

function handleField(fieldName) {
  const error = validateField(fieldName);
  if (error) showError(fieldName, error);
  else clearError(fieldName);
}

⑤ 提交通关:全量验证

javascript 复制代码
form.addEventListener('submit', (e) => {
  e.preventDefault();
  let firstError = '';
  Object.keys(RULES).forEach(name => {
    const err = validateField(name);
    if (err) { showError(name, err); firstError = firstError || name; }
    else clearError(name);
  });
  if (firstError) {
    document.getElementById(firstError).focus();   // 定位到第一个错误字段
    return;
  }
  // 全部通过 → 收集数据,发送请求
  const fd = new FormData(form);
  console.log('提交数据:', Object.fromEntries(fd));
});

优势

  • 新增字段只需在 RULES 中加一行,事件绑定和校验逻辑无需改动
  • 实时、失焦、提交三种模式共用同一套校验代码。
  • 可以方便地扩展更多校验类型(如跨字段校验)。

6. 完整示例:学生新增表单

结合以上所有知识,一个典型的学生信息录入表单将包含:

  • 实时校验学号、姓名、年龄、手机号。
  • 班级下拉框(使用 change 事件)检查是否选择。
  • 提交时再次全面检查,并利用 FormData 收集数据。
  • 实时统计通过项数,控制提交按钮状态。

核心代码无需再重复,只需按照上述模式将具体字段规则填入 RULES,并绑定事件即可。完整实现可参考代码文件 05-student-form.html(双击运行,无需服务器)。

关键细节补充

  • 下拉框必选 :将默认提示项设置为 <option value="">请选择班级</option>,配合 required: true,值依然为空时会触发"不能为空"。
  • 空字符串陷阱Number('') 结果为 0,因此年龄必填检查必须在转换前进行。
  • 复用思想handleField 既被实时/失焦事件调用,也可以用在提交后的全量检查中(只需遍历 RULES 调用 validateField)。

7. 小结

  • 数据获取 :整体提交首选 FormData(必须带 name 属性),少量字段或需类型转换时手动取值。
  • 校验时机 :实时校验(input)提供最佳体验,失焦校检(blur)减少干扰,提交时全局把关。
  • 校验规则 :使用规则表集中定义,再通过统一函数执行,避免散落的条件判断,易于扩展和维护。
  • 与后端对照:前端校验是后端校验的补充,不是替代,最终安全永远在后端。

掌握了表单校验,你就能构建出既友好又稳健的数据录入界面。

相关推荐
张元清1 小时前
React useEvent Hook:告别过期闭包的稳定回调 (2026)
javascript·react.js
用户938515635071 小时前
从 DOM 编程到声明式 UI:React useRef 与 useState 底层全解析
前端·javascript·react.js
小月土星1 小时前
19.LeetCode 删除链表的倒数第 N 个节点 超详细题解(JS 版)
javascript
kisshyshy1 小时前
前端路由进化史:从刷新白屏到SPA,手写一个Hash路由就懂了!
前端·javascript·react.js
保加利亚的风2 小时前
Docker 学习文档(Mac + Docker Desktop 版)
前端·后端
java1234_小锋2 小时前
Vue3专题 - 条件渲染
前端·javascript·vue.js
明月_清风3 小时前
🚀 AI Agent 完全入门指南:从 LLM 到生产落地,新手必懂的 34 个核心概念
前端·后端·ai编程
嘟嘟07173 小时前
从零理解 useRef:React 中操作 DOM 与持久化变量的唯一桥梁
javascript