JavaScript表单验证源代码:正则表达式与错误提示实例

作者:白露时间:2026-11-03 09:16:46浏览:237

提供完整的JavaScript表单验证源代码实例,涵盖用户名、电子邮箱、手机号及密码强度的正则表达式规则设计,解析失焦即时校验、错误提示DOM渲染与提交时全量拦截的实现机制。

技术教程 主题:客户端表单校验 核心技术:RegExp / DOM / EventListener
JavaScript表单验证源代码:正则表达式与错误提示实例

在Web前端开发中,表单验证是拦截无效输入、提升交互质量的第一道关卡。一个健壮的验证模块既需要精确的正则表达式以确保数据合规,也需要清晰及时的错误提示UI,告知用户未通过的原因及修改方向。本文提供一套基于原生JavaScript编写的完整表单验证源代码,包含常用输入项的正则表达式配置、失焦与提交双重校验逻辑,以及对应的DOM错误提示状态渲染方案。

一、表单验证架构与执行流程设计

表单验证的常见误区在于“只在点击提交按钮时才报错”或“边输入边频繁弹出警报”。良好的用户体验应当采取分层验证策略:单字段失焦(blur)触发局部核验,用户专注于某个输入项时不过早打断;输入过程中(input)在检测到合规后即时撤销错误样式;而在用户触发提交(submit)时,执行全局聚合遍历,若有任意项未通过,则拦截表单提交并自动将光标聚焦于首个错误输入框。

JavaScript表单验证源代码:正则表达式与错误提示实例
图1:JavaScript表单验证事件驱动与执行生命周期流程图

整个验证流程的核心执行步骤如下:

  1. 事件拦截:在表单元素上监听 submit 事件,通过 event.preventDefault() 拦截浏览器默认的原生提交行为,将提交控制权完全交给JavaScript脚本。
  2. 字段映射与规则匹配:为每个输入控件配置独立的校验器对象,包含非空检测、正则表达式规则、最大/最小长度边界以及对应的错误提示文案。
  3. UI状态同步:当校验失败时,向输入框挂载 is-invalid 状态类,更新对应的 aria-invalid="true" 无障碍属性,并在相邻的提示容器中注入具体错误文案;当校验通过时,清除错误状态并重置文本。

二、核心字段正则表达式设计与匹配规则

正则表达式(Regular Expression)是检测文本结构是否符合特定格式的核心工具。在构建表单验证规则时,必须显式声明起始符 ^ 与结束符 $,确保对用户输入的整行内容进行全量匹配,避免因部分子串匹配而放行非法输入。

JavaScript表单验证源代码:正则表达式与错误提示实例
图2:关键字段正则表达式结构与字符边界匹配原理图
验证字段 正则表达式 (Pattern) 规则说明与边界约束
用户名 / 账号 ^[a-zA-Z0-9_一-龥]{3,16}$ 允许中文字符、英文字母、阿拉伯数字及下划线,字符总长度限制在3到16位之间。
电子邮箱 ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ 以字母数字或常见点号开头,强制包含 @ 符号,主机域名后紧跟点号与至少2位字母构成的顶级域名。
手机号码 ^1[3-9]\d{9}$ 匹配中国大陆11位手机号:第一位必须为数字1,第二位限定在3至9之间,后续连续跟随9位任意数字。
强密码规则 ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d@$!%*?&]{8,20}$ 使用正向前瞻断言 (?=...) 约束密码必须同时包含小写字母、大写字母与数字,总长度为8到20位。
注意:在JavaScript中通过字面量(/pattern/)声明正则表达式时,无需对常规反斜杠进行双重转义;但如果通过 new RegExp("...") 构造函数动态生成规则,字符串参数中的反斜杠必须写为 \d。推荐在规则固定的场景下优先使用字面量语法。

三、输入框状态机与可访问性错误提示机制

交互式错误提示不能仅仅停留在“把文字变成红色”,这不仅在视觉上缺乏层次感,对于使用屏幕阅读器的视障用户也是不可感知的。一套规范的错误提示体系应当在DOM结构与无障碍规范(WAI-ARIA)上达成闭环。

JavaScript表单验证源代码:正则表达式与错误提示实例
图3:输入框验证状态迁移与DOM / WAI-ARIA属性映射机制

在设计HTML与CSS交互时,建议遵循以下三项规范:

  • 关联提示容器:在每个 <input> 标签中设置 aria-describedby="[字段ID]-error",并在下方的错误提示容器上标记 id="[字段ID]-error"。屏幕阅读器在光标移入时能直接读取该输入框关联的错误状态。
  • 动态属性开关:当校验未通过时,为输入框赋予 aria-invalid="true";校验通过或输入框为空时恢复为 aria-invalid="false"。
  • 平滑清除机制:监听输入框的 input 事件,当用户重新键入内容且内容逐步符合规范时,应当即时隐藏红色提示文本,消除用户的挫败感。

四、完整表单验证源代码实例

以下代码提供了一个开箱即用的完整示例,包含注册表单的HTML标记、CSS状态样式以及模块化组织的JavaScript校验脚本。你可以将以下代码直接保存为 .html 文件在浏览器中打开验证。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>用户注册表单验证实例</title>
  <style>
    /* 表单基础排版 */
    .form-wrapper {
      max-width: 420px;
      margin: 40px auto;
      padding: 24px;
      background: #FFFFFF;
      border: 1px solid #CCD2D8;
      border-radius: 8px;
      box-shadow: 0 4px 10px rgba(79, 98, 114, 0.1);
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    }
    .form-wrapper h2 {
      margin-bottom: 20px;
      font-size: 20px;
      color: #4F6272;
      text-align: center;
    }
    .form-group {
      margin-bottom: 18px;
      display: flex;
      flex-direction: column;
    }
    .form-label {
      font-size: 13px;
      font-weight: 600;
      color: #4F6272;
      margin-bottom: 6px;
    }
    .form-control {
      height: 38px;
      padding: 6px 12px;
      font-size: 14px;
      border: 1px solid #CCD2D8;
      border-radius: 4px;
      outline: none;
      transition: border-color 0.2s, box-shadow 0.2s;
    }
    .form-control:focus {
      border-color: #477DAD;
      box-shadow: 0 0 0 3px rgba(71, 125, 173, 0.2);
    }
    /* 校验状态样式 */
    .form-control.is-invalid {
      border-color: #D9534F;
      background-color: #FFF9F9;
    }
    .form-control.is-invalid:focus {
      box-shadow: 0 0 0 3px rgba(217, 83, 79, 0.2);
    }
    .form-control.is-valid {
      border-color: #477DAD;
    }
    .error-feedback {
      display: none;
      margin-top: 5px;
      font-size: 12px;
      color: #D9534F;
      line-height: 1.4;
    }
    .form-group.has-error .error-feedback {
      display: block;
    }
    .btn-submit {
      width: 100%;
      height: 40px;
      background-color: #477DAD;
      color: #FFFFFF;
      border: none;
      border-radius: 4px;
      font-size: 15px;
      font-weight: 600;
      cursor: pointer;
      transition: background-color 0.2s;
    }
    .btn-submit:hover {
      background-color: #3A6790;
    }
    .btn-submit:disabled {
      background-color: #9AA5B1;
      cursor: not-allowed;
    }
  </style>
</head>
<body>

<div class="form-wrapper">
  <h2>账号注册</h2>
  <form id="registerForm" novalidate>
    <!-- 用户名 -->
    <div class="form-group" id="group-username">
      <label class="form-label" for="username">用户名</label>
      <input type="text" id="username" class="form-control" autocomplete="off"
             placeholder="3-16位汉字、字母、数字" aria-describedby="username-error" aria-invalid="false">
      <span class="error-feedback" id="username-error" role="alert"></span>
    </div>

    <!-- 电子邮箱 -->
    <div class="form-group" id="group-email">
      <label class="form-label" for="email">电子邮箱</label>
      <input type="email" id="email" class="form-control" autocomplete="off"
             placeholder="example@domain.com" aria-describedby="email-error" aria-invalid="false">
      <span class="error-feedback" id="email-error" role="alert"></span>
    </div>

    <!-- 手机号码 -->
    <div class="form-group" id="group-phone">
      <label class="form-label" for="phone">手机号码</label>
      <input type="tel" id="phone" class="form-control" autocomplete="off"
             placeholder="11位手机号码" aria-describedby="phone-error" aria-invalid="false">
      <span class="error-feedback" id="phone-error" role="alert"></span>
    </div>

    <!-- 登录密码 -->
    <div class="form-group" id="group-password">
      <label class="form-label" for="password">登录密码</label>
      <input type="password" id="password" class="form-control"
             placeholder="8-20位,含大小写字母与数字" aria-describedby="password-error" aria-invalid="false">
      <span class="error-feedback" id="password-error" role="alert"></span>
    </div>

    <!-- 确认密码 -->
    <div class="form-group" id="group-confirmPwd">
      <label class="form-label" for="confirmPwd">确认密码</label>
      <input type="password" id="confirmPwd" class="form-control"
             placeholder="请再次输入登录密码" aria-describedby="confirmPwd-error" aria-invalid="false">
      <span class="error-feedback" id="confirmPwd-error" role="alert"></span>
    </div>

    <button type="submit" class="btn-submit" id="submitBtn">提交注册</button>
  </form>
</div>

<script>
// 验证规则配置字典
const validationRules = {
  username: {
    required: true,
    pattern: /^[a-zA-Z0-9_一-龥]{3,16}$/,
    emptyMsg: "请输入用户名",
    errorMsg: "用户名须为3-16位汉字、字母、数字或下划线"
  },
  email: {
    required: true,
    pattern: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
    emptyMsg: "请输入电子邮箱",
    errorMsg: "请输入格式正确的电子邮箱地址"
  },
  phone: {
    required: true,
    pattern: /^1[3-9]\d{9}$/,
    emptyMsg: "请输入手机号码",
    errorMsg: "请输入有效的11位中国大陆手机号码"
  },
  password: {
    required: true,
    pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d@$!%*?&]{8,20}$/,
    emptyMsg: "请设置登录密码",
    errorMsg: "密码长度须为8-20位,且必须包含大小写字母与数字"
  },
  confirmPwd: {
    required: true,
    customValidator: (value) => {
      const pwd = document.getElementById("password").value;
      if (!value) return { valid: false, msg: "请再次输入密码以确认" };
      if (value !== pwd) return { valid: false, msg: "两次输入的密码不一致" };
      return { valid: true };
    }
  }
};

// 显示错误提示
function showError(fieldId, message) {
  const input = document.getElementById(fieldId);
  const group = document.getElementById(`group-${fieldId}`);
  const errorSpan = document.getElementById(`${fieldId}-error`);

  input.classList.remove("is-valid");
  input.classList.add("is-invalid");
  input.setAttribute("aria-invalid", "true");

  errorSpan.textContent = message;
  group.classList.add("has-error");
}

// 清除错误提示并标记合法
function clearError(fieldId, markValid = true) {
  const input = document.getElementById(fieldId);
  const group = document.getElementById(`group-${fieldId}`);
  const errorSpan = document.getElementById(`${fieldId}-error`);

  input.classList.remove("is-invalid");
  input.setAttribute("aria-invalid", "false");
  errorSpan.textContent = "";
  group.classList.remove("has-error");

  if (markValid && input.value.trim() !== "") {
    input.classList.add("is-valid");
  } else {
    input.classList.remove("is-valid");
  }
}

// 单字段校验逻辑
function validateField(fieldId) {
  const config = validationRules[fieldId];
  const input = document.getElementById(fieldId);
  if (!config || !input) return true;

  const value = input.value.trim();

  // 1. 自定义校验函数优先执行(如确认密码比对)
  if (typeof config.customValidator === "function") {
    const result = config.customValidator(value);
    if (!result.valid) {
      showError(fieldId, result.msg);
      return false;
    }
    clearError(fieldId);
    return true;
  }

  // 2. 必填性判断
  if (config.required && value === "") {
    showError(fieldId, config.emptyMsg);
    return false;
  }

  // 3. 正则表达式模式匹配
  if (config.pattern && !config.pattern.test(value)) {
    showError(fieldId, config.errorMsg);
    return false;
  }

  // 校验全部通过
  clearError(fieldId);
  return true;
}

// 初始化事件绑定
document.addEventListener("DOMContentLoaded", () => {
  const form = document.getElementById("registerForm");
  const fieldKeys = Object.keys(validationRules);

  // 为每个字段绑定 blur (失焦校验) 与 input (边输边清除错误)
  fieldKeys.forEach((key) => {
    const input = document.getElementById(key);
    if (!input) return;

    input.addEventListener("blur", () => {
      validateField(key);
    });

    input.addEventListener("input", () => {
      // 用户正在输入时,如果输入框处于错误态,且当前值已满足正则,则清除错误
      const value = input.value.trim();
      const config = validationRules[key];
      if (config.pattern && config.pattern.test(value)) {
        clearError(key);
      }
    });
  });

  // 表单全量提交校验
  form.addEventListener("submit", (e) => {
    e.preventDefault(); // 拦截默认提交行为

    let isFormValid = true;
    let firstErrorField = null;

    // 遍历所有规则字段
    fieldKeys.forEach((key) => {
      const isValid = validateField(key);
      if (!isValid) {
        isFormValid = false;
        if (!firstErrorField) {
          firstErrorField = document.getElementById(key);
        }
      }
    });

    // 若有字段校验不通过,自动将焦点移动至第一个错误输入框
    if (!isFormValid) {
      if (firstErrorField) {
        firstErrorField.focus();
      }
      return;
    }

    // 全部验证通过,执行数据提交业务
    alert("表单验证全部通过,数据准备提交!");
  });
});
</script>
</body>
</html>

五、表单提交批量聚合校验与执行总结

在表单提交阶段,不能只检测并提示第一个错误就提前中断循环。最佳的交互实践是:全量执行校验,让所有出错字段的错误提示同时展示给用户,同时将浏览器视口与输入光标自动平滑滚动并聚焦(focus)到首个错误项上。这样既能让用户一次性获知所有需要修正的信息,又明确指示了下一步的操作入口。

JavaScript表单验证源代码:正则表达式与错误提示实例
图4:表单提交批量聚合校验与首错聚焦控制架构总结图

六、常见边界问题与防御处理

在实际生产部署中,除了常规的格式正则匹配,还有几处细节需要特别注意:

  • HTML5原生提示拦截:表单标签必须添加 novalidate 属性(例如 <form id="registerForm" novalidate>)。若不添加,浏览器自带的气泡弹窗提示会干扰自定义的 error-feedback 容器展示,导致UI冲突。
  • 首尾空白字符注入:在进行 test() 正则判断前,务必调用 value.trim()。用户在移动端通过输入法自动补全或复制内容时,经常携带尾部空格,若不清理会导致本该合规的内容验证失败。
  • 关联字段依赖校验:“确认密码”高度依赖“设置密码”字段。当用户修改了“设置密码”后,即便没有点击“确认密码”框,在提交时也必须重新触发一次一致性比对。
  • 防重复点击与网络并发:在全量验证通过并发起 fetch() 异步请求期间,应立即设置 submitBtn.disabled = true 并添加“提交中...”文案,防止用户因网络延迟连续多次点击产生重复提交。
作者最新文章

相关标签:

相关文章
更多