Skip to content

正则表达式基本语法与JavaScript 中使用正则表达式(正则表达式 2/3) ​

本页对应 课堂视频,共 5 段视频,约 1 小时 1 分。

学习目标 ​

  • 能使用 i、g、m 模式修饰符完成大小写不敏感匹配、全局匹配和多行匹配,并说明各自作用范围。
  • 能调用正则对象的 test() 与 exec() 对字符串进行验证或提取,并正确解释布尔值、数组、null 等返回结果。
  • 能使用字符串对象的 search()、match()、replace()、split() 配合正则完成查找、提取、替换和分割,并说明返回值差异。
  • 能识别正则中的模式单元(括号),说明其内容在 match()/exec() 结果中会被单独匹配或提取。
  • 能实现用户名、邮箱、密码、确认密码的表单验证:在失焦时逐项提示,在提交时再次调用验证函数并用返回值与逻辑与控制是否阻止默认提交。

知识点 ​

模式修饰符:i、m、g ​

为什么需要:同一个正则,常常要处理大小写、多行文本、全局替换等不同场景。修饰符写在正则字面量末尾,用来改变匹配规则。怎么想:i 管大小写,m 管多行下的 ^ 和 $,g 管全局。怎么做:

js
console.log(/hello/.test("Hello"));  // false
console.log(/hello/i.test("Hello")); // true

console.log(/^hello$/.test("hello\nworld"));  // false
console.log(/^hello$/m.test("hello\nworld")); // true

m 不是把 \n 删掉,而是让每一行都被当作独立字符串看待,所以 ^ 能匹配行首、$ 能匹配行尾。g 在 replace 里最直观:"666".replace(/6/g, "六") 才会全部替换。易错点:m 只影响 ^ 和 $,不要以为它影响所有匹配;g 不加时,replace 默认只替换第一个。

正则对象:test 与 exec ​

为什么需要:正则对象要验证字符串,就要有方法。test 适合纯验证,exec 适合把匹配到的内容取出来。怎么想:正则对象的方法参数是字符串;返回值不同:test 返回布尔值,exec 成功返回数组,失败返回 null。怎么做:

js
const reg1 = /\d{6}/;
const reg2 = new RegExp("\\d{6}");
const reg3 = new RegExp(/\d{6}/);

console.log(reg1.test("123456")); // true
console.log(reg1.exec("abc123456")); // 数组

从输入框拿正则时,拿到的 value 是字符串,不能直接 .test(),要转成正则对象:

js
const reg = new RegExp(regInput.value);
console.log(reg.test(strInput.value));

易错点:在字符串里写正则内容,反斜杠要双写,如 "\\d{6}";但从输入框拿到的 \d 就是 \d,不用再双写。exec 在本节演示中只取第一个匹配,不是一次性全局提取;普通括号捕获的内容在数组下标里,不在 groups 里。

字符串对象:search、match、replace、split ​

为什么需要:匹配不只发生在正则对象上,字符串也能主动接收正则参数。怎么想:字符串方法参数传正则,形成“字符串 + 正则”的配合。怎么做:

js
console.log("abc123".search(/\d{3}/)); // 3,失败返回 -1
console.log("abc123".match(/\d{3}/));  // 数组,失败返回 null
console.log("666".replace(/6/g, "六")); // 六六六
console.log("小乐,老赖儿.大个儿;刘姥姥".split(/[,.;]/));

search 返回第一个匹配部分的索引;match 成功返回数组,失败返回 null;replace 默认只替换一个,要全部替换加 g;split 可以用正则表示多个分隔符。易错点:search 失败是 -1,不是 0;match 失败是 null;replace 不加 g 只换第一个。

模式单元:括号捕获 ​

为什么需要:有时不仅要匹配整体,还要把其中一部分单独取出来。怎么想:用小括号把一部分包起来,形成模式单元。怎么做:

js
const r = /(a)([a-z]*)/i;
console.log(r.exec("Amet"));

返回数组第 0 项是整体匹配,后面的下标是括号捕获到的内容。易错点:普通捕获组不会自动放进 groups;groups 对应的是命名分组。全局 match 和 exec 的结果形式有差异,写代码时要看实际返回。

表单验证案例 ​

为什么需要:只靠失焦验证,用户可能不逐个输入就直接点提交,所以要做双重验证。怎么想:每个输入框失焦时调用验证函数并显示提示;表单提交时再统一调用所有验证函数,用 && 汇总。怎么做:

js
form.onsubmit = function () {
  return checkUserName() && checkEmail() && checkPwd() && checkRePwd();
};

userInput.onblur = checkUserName; // 回调函数不加括号

验证函数成功 return true,失败 return false;提交事件里返回 false 可阻止默认提交。密码可只验证长度:

js
pwdInput.onblur = checkPwd;
function checkPwd() {
  return pwdInput.value.length >= 6 && pwdInput.value.length <= 18;
}

确认密码只判断是否一致:pwdInput.value === rePwdInput.value。易错点:事件回调不能写成 checkUserName(),否则会立即调用;提交汇总时每个函数都要有返回值;从输入框拿正则内容要用 new RegExp(...) 转成正则对象。

本节把“在 JavaScript 中使用正则表达式”拆成两条线:一条是正则本身怎么创建、怎么带修饰符;另一条是正则对象和字符串对象如何配合完成验证、提取、替换和分割。转写稿里的“政策/正策”统一纠正为“正则”,“ESEC/ESDC”统一为 exec。

模式修饰符:i、m、g ​

为什么需要:同一个正则,大小写、单行/多行、只匹配一次/全局匹配,结果可能完全不同。
怎么想:i 管大小写不敏感;m 让 ^ 和 $ 匹配每一行的开头和结尾;g 管全局匹配,常配合替换使用。
怎么做:

js
let reg = /hello/i;
console.log(reg.test("Hello")); // true

let msg = "hello\nworld\nhello";
console.log(/^hello$/.test(msg));  // false
console.log(/^hello$/m.test(msg)); // true

易错点:m 不是把字符串变成数组,而是改变 ^、$ 的含义;g 在 replace 里最直观,不加 g 默认只替换第一个。

创建正则对象的三种方式 ​

为什么需要:有时正则是写死的,有时来自输入框,必须把字符串转成正则对象。
怎么想:直接量最简单;RegExp() 和 new RegExp() 适合把字符串转成正则。
怎么做:

js
let r1 = /\d{6}/;
let r2 = RegExp("\\d{6}");
let r3 = new RegExp("\\d{6}");
console.log(r1, r2, r3);

易错点:在字符串字面量里写 \d 要写成 \\d,否则反斜杠会被字符串先转义;但从输入框拿到的内容,用户写 \d{6} 就是 \d{6},直接 new RegExp(input.value) 即可,不要再多加一层转义。

正则对象方法:test 与 exec ​

为什么需要:验证格式用 test 最直接;想拿到匹配内容用 exec。
怎么想:test 返回布尔值;exec 成功返回数组,失败返回 null。数组里 [0] 是整体匹配内容,index 是位置,input 是原字符串。
怎么做:

js
let reg = /\d{6}/;
console.log(reg.test("abc123456")); // true
console.log(reg.exec("abc123456")); // 数组
console.log(reg.exec("abc"));       // null

易错点:exec 失败是 null,不是 false;exec 即使加了 g,也不会一次性返回全部结果,只取第一个,批量提取更适合用字符串的 match 加 g。

字符串对象方法:search、match、replace、split ​

为什么需要:正则对象负责“规则”,字符串方法负责“在字符串上执行规则”。
怎么想:search 找位置;match 取匹配数组;replace 做替换;split 用正则做分隔符。
怎么做:

js
let msg = "hello hello 666123888";
console.log(msg.search(/\d{6}/)); // 首次匹配位置,失败 -1
console.log(msg.match(/\d{6}/));  // 数组,失败 null

console.log("666123888".replace(/\d/g, "0")); // 所有数字变 0
console.log("小乐,老赖儿.大个儿;刘姥姥".split(/[,.;]/));

易错点:replace 不加 g 只替换第一个;match 加 g 后只返回整体匹配数组,不再返回捕获组;split 用正则时要注意点号等特殊字符。

模式单元为什么会被单独匹配 ​

为什么需要:有时不只想要整体匹配,还想把其中一段单独拿出来。
怎么想:小括号就是模式单元,也叫捕获组。非全局 match/exec 会把整体和捕获组一起返回;全局 match 通常只返回整体。
怎么做:

js
let str = "Apple ant Axe";
let reg = /a([a-z]+)/i;
console.log(str.match(reg));      // 整体 + 捕获组
console.log(str.match(/a([a-z]+)/ig)); // 全局只返回整体

易错点:groups 为空通常是因为没有命名捕获组;不要以为加了括号就一定影响全局匹配结果,全局 match 下捕获组信息会丢。

表单验证:单字段验证与提交拦截 ​

为什么需要:只在 blur 时验证,用户不填直接点提交仍可能绕过。
怎么想:每个字段写一个 checkXxx 函数,返回布尔值;失去焦点时调用;提交时再用 && 串起来,全部通过才允许提交。
怎么做:

js
function checkUserName() {
  let val = userNameInput.value;
  let reg = /^\w{4,}$/;
  return reg.test(val);
}

function checkPWD() {
  let len = pwdInput.value.length;
  return len >= 6 && len <= 18;
}

function checkRePWD() {
  return rePwdInput.value === pwdInput.value;
}

userNameInput.onblur = checkUserName; // 回调函数不加括号
form.onsubmit = function () {
  return checkUserName() && checkEmail() && checkPWD() && checkRePWD();
};

易错点:事件回调写 checkUserName,不要写 checkUserName();提交拦截要 return false 才能阻止默认提交;每个验证函数必须 return 布尔值,否则 && 串联拿不到结果;密码只限制长度时可以直接用 value.length,不必硬写正则。

材料核对与无法扩写说明 ​

你给到的“本节笔记”标题是 《事件的防抖和节流》,但随附转写稿的实际内容从头到尾讲的是:

  • 正则表达式的模式修饰符:i、g、m
  • 正则对象方法:test、exec
  • 字符串对象方法:search、match、replace、split
  • 模式单元内容被单独匹配
  • 表单验证案例:用户名、邮箱、密码、确认密码、提交时双重验证

转写稿中没有出现“防抖”“节流”“debounce”“throttle”“定时器”“频繁触发”“最后一次执行”“固定间隔执行”等任何与本节标题对应的知识点。同时,你提供的笔记正文里只有标题,没有图片,也没有代码片段。因此,如果按“防抖和节流”扩写,我只能凭空编造概念、推导和调试过程,这违反“不得编造”的要求;如果按转写稿扩写,则教程主题会变成“正则表达式与表单验证”,与本节笔记标题不一致。

目前不能直接产出的原因 ​

  1. 主题不匹配:笔记标题与转写稿内容完全错位。
  2. 缺少防抖节流的原始材料:没有该节的概念解释、示例代码、调试过程、易错点。
  3. 缺少图片与代码:要求保留“本节笔记里的图片和代码”,但当前笔记中未提供。
  4. 不能补造推导:防抖节流的“为什么需要→怎么想→怎么做→易错点”必须来自给定材料,不能由我虚构。

你可以这样补充材料 ​

请任选一种方式继续:

  • 方式一:提供真正的《事件的防抖和节流》转写稿与笔记代码、图片;
  • 方式二:确认把题目改为“正则表达式与表单验证”,我就可以严格依据现有转写稿扩写成 1000–1800 字教程正文,并使用 ### 小标题;
  • 方式三:只提供防抖节流的代码片段和课堂笔记,我可以据此补写推导与调试过程。

若改题可扩写的现有内容范围 ​

在不改题的情况下,现有转写稿只能支撑如下小标题:

  • ### 模式修饰符 i、g、m 的作用
  • ### 正则对象 test 与 exec 的区别
  • ### 字符串 search、match、replace、split 的使用
  • ### 模式单元为何会被单独匹配
  • ### 表单验证中的失焦验证与提交验证
  • ### 为什么提交时还要做双重验证

这些内容与“事件的防抖和节流”无关。为避免编造,我不能把它们包装成防抖节流教程。

请补充正确的转写稿或确认改题后,我再按你要求输出正文。

范围说明 ​

本节笔记主题是“防抖 debounce”。需要说明的是:所附转写稿实际在讲正则模式修饰符、正则对象方法、字符串对象方法、模式单元和表单验证案例,并没有防抖的推导与调试记录。为避免编造,下面不把正则内容硬套到防抖上,只依据笔记中的定义和应用场景扩写;笔记未附图片和代码,因此不补“原图/原代码”,文末实现示例仅用于说明防抖思路。

防抖是什么 ​

为什么需要:持续触发一个事件时,如果每次触发都立刻执行回调,会造成大量重复计算或请求。例如输入框每敲一个字符就发请求,窗口每变一次大小就重算布局,都会浪费资源。防抖的目标是:等用户“停下来”一段时间后,只执行最后一次。

怎么想:把每次触发都看成一一次“重新计时”。在 n 秒内事件没有再次触发,才执行回调;如果 n 秒内又触发了事件,就清除上一次计时,重新开始计时。关键不是“限制触发频率”,而是“等停止后再执行”。

怎么做:用一个定时器保存计时状态。每次事件触发时,先清除上一次定时器,再设置新的定时器;只有最后一次定时器能走到回调。定时器变量要放在闭包里,让多次事件共享同一个计时状态。

易错点:忘记 clearTimeout 会导致每次触发都排一个定时器,最后执行多次;把定时器变量写在事件回调内部,每次都是新变量,无法清除上一次;误把防抖当节流,防抖只保证“停止后执行一次”,不保证固定间隔执行。

搜索联想场景 ​

为什么需要:用户在搜索框不断输入时,如果每个 input 事件都请求接口,请求次数会非常多,而且前一次结果可能还没回来,后一次输入又发出请求,既浪费资源也容易造成结果错乱。

怎么想:用户连续输入时,中间状态不需要立刻请求;等用户停止输入一小段时间,比如 500ms,再拿当前输入值请求。这样只对最后一次输入负责。

怎么做:监听输入框的 input 事件,把“发请求”的回调交给防抖函数包装;防抖延迟根据业务设置。请求回调里读取当前输入值,而不是在绑定事件时提前读取。

易错点:延迟设置过短,防抖效果不明显;延迟过长,用户会觉得卡顿;请求回调里如果使用旧变量,可能拿到旧值;防抖不能替代请求竞态处理,必要时还要取消旧请求或忽略旧响应。

resize 场景 ​

为什么需要:调整浏览器窗口大小时,resize 会连续触发很多次。如果每次都重新计算布局、重绘图表,容易造成卡顿。

怎么想:用户拖动窗口的过程中,中间尺寸不一定都要处理;等用户停止调整后再执行一次重算,更符合实际需要。

怎么做:给 window 的 resize 事件绑定防抖后的处理函数,延迟可设为 200ms 左右;回调里再读取最新窗口尺寸并执行布局计算。

易错点:在回调外提前缓存尺寸,导致拿到旧值;防抖函数每次组件重新渲染都重新创建,造成多个定时器;页面卸载时没有清理定时器。

点赞、提交表单场景 ​

为什么需要:点赞、提交表单等操作如果被连续点击,可能重复提交,产生重复数据。防抖可以让短时间内的多次点击只执行最后一次。

怎么想:用户连续点击时,不立刻提交;等一小段时间没有再次点击,再提交一次。这样能减少误触和连点。

怎么做:把提交逻辑用防抖包装后绑定到按钮或表单提交事件;提交回调里执行校验和发送。注意防抖只是前端减少重复触发,重要提交仍要配合按钮禁用、后端幂等等措施。

易错点:认为防抖能完全防止重复提交;在提交成功后没有重置状态;把防抖延迟设得太长,用户以为没反应;表单提交还要处理默认行为和校验失败。

实现思路示例 ​

js
function debounce(fn, delay) {
  let timer = null;

  return function (...args) {
    if (timer) {
      clearTimeout(timer);
    }

    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

调试时可以分别在原始事件回调和防抖后的回调里打日志:原始事件会连续打印多次,防抖回调应在停止触发 delay 毫秒后只打印一次。若打印多次,优先检查 timer 是否在闭包中共享、是否每次触发都清除了上一次定时器、是否重复绑定了事件。

课堂画面 ​

课堂画面 1

课堂画面 2

课堂画面 3

课堂画面 4

课堂画面 5

代码示例 ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>正则基本语法与常用方法</title>
</head>
<body>
<script>
  /* ---------- 1. 模式修饰符 i / m ---------- */
  var reg = /hello/;
  console.log(reg.test('Hello'));       // false:默认区分大小写
  console.log(/hello/i.test('Hello'));  // true :i 忽略大小写
  console.log(/hello/i.test('HELLO'));  // true :大小写都不区分

  var msg = 'hello\nworld\n123';        // 字符串里用 \n 表示换行
  console.log(/^hello$/.test(msg));     // false:默认 ^ $ 只对应整个字符串的首尾
  console.log(/^hello$/m.test(msg));    // true :m 多行模式,每一行都被当成独立字符串

  /* ---------- 2. 正则对象方法 test / exec ---------- */
  var reg01 = /\d{6}/;      // 包含 6 位连续数字即可
  var reg02 = /^\d{6}$/;    // 必须整体就是 6 位数字
  var str = 'hello666123888sb';

  console.log(reg01.test(str));   // true
  console.log(reg02.test(str));   // false:有首尾锚点,整体不满足
  console.log(reg01.exec(str));   // ['666123', index: 5, input: 'hello666123888sb', groups: undefined]
  console.log(reg02.exec(str));   // null:匹配失败返回 null

  /* ---------- 3. 字符串对象方法 search / match / replace / split ---------- */
  console.log(str.search(/\d{6}/));     // 5   :返回匹配内容的起始索引
  console.log(str.search(/^\d{6}$/));   // -1  :匹配不到返回 -1
  console.log(str.match(/\d{6}/));      // ['666123', index: 5, ...] 结果与 exec 一致
  console.log(str.match(/^\d{6}$/));    // null
  console.log(str.replace(/6/g, '六')); // hello六六六123888sb  g 实现全局替换
  console.log(str.replace(/\d/g, '零')); // 所有数字都被替换成“零”

  /* ---------- 4. 模式单元的内容会被单独匹配出来 ---------- */
  console.log('Amet abc Add'.match(/A[a-z]+/));     // ['Amet', index: 0, ...]
  console.log('Amet abc Add'.match(/(A)([a-z]+)/)); // 整体结果 + 模式单元内容单独列出
  console.log('Amet abc Add'.match(/A[a-z]+/g));    // ['Amet', 'Add'] 全局匹配取多个

  /* ---------- 5. split 用正则当分隔符 ---------- */
  var names = '小乐,老赖,大个。刘姥姥;马姥姥,司马姥姥';
  console.log(names.split(/[,。;]/));  // ["小乐","老赖","大个","刘姥姥","马姥姥","司马姥姥"]
</script>
</body>
</html>

要点与易错点:

  • i 只影响大小写,m 只影响 ^ 和 $ 的匹配范围(从“整个字符串”变成“每一行”),g 影响的是“是否全局”,三者可以叠加写。
  • test 返回布尔值,适合纯验证;exec 匹配成功返回数组(含 index、input),失败返回 null,更适合从大段文本里“提取”。
  • 加不加 ^ $ 差别巨大:/\d{6}/ 只要“包含”就通过,/^\d{6}$/ 要求“整体”就是 6 位数字。
  • search 返回的是索引,找不到返回 -1,不要和 test 的 false 混用。
  • replace 不加 g 只替换第一个,必须加 g 才全局替换。
  • split 的参数可以写正则,/[,。;]/ 表示“逗号、句号、分号任意一个都算分隔符”,一个原子能匹配多个字符。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>正则匹配计算器</title>
  <style>
    input { display: block; margin: 10px 0; padding: 5px; border: 1px solid #999; }
    button { padding: 5px 20px; }
  </style>
</head>
<body>
  <input id="regInput" placeholder="请输入正则表达式,例如:\d{6}">
  <input id="strInput" placeholder="请输入字符串">
  <button id="btn">验证</button>

<script>
  var regInput = document.querySelector('#regInput');
  var strInput = document.querySelector('#strInput');
  var btn = document.querySelector('#btn');

  btn.onclick = function () {
    var regStr = regInput.value;   // 输入框拿到的永远是字符串
    var str = strInput.value;
    var reg = new RegExp(regStr);  // 字符串 → 正则对象
    console.log(reg.test(str));    // 布尔值
    console.log(reg.exec(str));    // 匹配结果数组 / null
  };
</script>
</body>
</html>

要点与易错点:

  • 输入框 value 拿到的一定是字符串类型,字符串身上没有 test 方法,直接调用会报“不是一个函数”。
  • 必须用 new RegExp(regStr) 把字符串转成正则对象,这才是正则对象两种创建方式(字面量、new RegExp)存在的意义。
  • 在代码里手写字符串时,'\d' 里的 \d 会被字符串先转义掉,要写成 '\\d';但从输入框读取的 \d 就是 \d 本身,不需要双斜杠——这是两种场景最容易混淆的地方。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>表单验证</title>
  <style>
    input { display: block; margin: 8px 0; padding: 5px; border: 1px solid #999; }
    span { margin-left: 10px; font-size: 12px; }
    .ok  { color: green; }
    .err { color: red; }
  </style>
</head>
<body>
<form id="form">
  <input id="usernameInput" placeholder="请输入用户名"><span id="usernameInfo"></span>
  <input id="emailInput"    placeholder="请输入邮箱"><span id="emailInfo"></span>
  <input id="pwdInput"      placeholder="请输入密码"><span id="pwdInfo"></span>
  <input id="rePwdInput"    placeholder="请确认密码"><span id="rePwdInfo"></span>
  <button type="submit">注册</button>
</form>

<script>
  var form          = document.querySelector('#form');
  var usernameInput = document.querySelector('#usernameInput');
  var usernameInfo  = document.querySelector('#usernameInfo');
  var emailInput    = document.querySelector('#emailInput');
  var emailInfo     = document.querySelector('#emailInfo');
  var pwdInput      = document.querySelector('#pwdInput');
  var pwdInfo       = document.querySelector('#pwdInfo');
  var rePwdInput    = document.querySelector('#rePwdInput');
  var rePwdInfo     = document.querySelector('#rePwdInfo');

  // 验证用户名:4~16 位字母、数字、下划线
  function checkUsername() {
    var reg = /^[a-zA-Z0-9_]{4,16}$/;
    if (reg.test(usernameInput.value)) {
      usernameInfo.innerHTML = '用户名可用';
      usernameInfo.className = 'ok';
      return true;
    }
    usernameInfo.innerHTML = '用户名格式不正确(4~16位字母、数字、下划线)';
    usernameInfo.className = 'err';
    return false;
  }

  // 验证邮箱
  function checkEmail() {
    var reg = /^[\w-]+@\w+(\.\w+){1,2}$/;
    if (reg.test(emailInput.value)) {
      emailInfo.innerHTML = '邮箱可用';
      emailInfo.className = 'ok';
      return true;
    }
    emailInfo.innerHTML = '邮箱格式不正确';
    emailInfo.className = 'err';
    return false;
  }

  // 验证密码:只限制长度 6~18 位
  function checkPwd() {
    var len = pwdInput.value.length;
    if (len >= 6 && len <= 18) {
      pwdInfo.innerHTML = '密码可用';
      pwdInfo.className = 'ok';
      return true;
    }
    pwdInfo.innerHTML = '密码必须是 6~18 位';
    pwdInfo.className = 'err';
    return false;
  }

  // 验证确认密码:只需和上面一致
  function checkRePwd() {
    if (rePwdInput.value === pwdInput.value) {
      rePwdInfo.innerHTML = '两次密码一致';
      rePwdInfo.className = 'ok';
      return true;
    }
    rePwdInfo.innerHTML = '两次密码不一致';
    rePwdInfo.className = 'err';
    return false;
  }

  // 失去焦点时分别验证:这里传的是函数名,不加小括号
  usernameInput.onblur = checkUsername;
  emailInput.onblur    = checkEmail;
  pwdInput.onblur      = checkPwd;
  rePwdInput.onblur    = checkRePwd;

  // 提交时再验证一遍,return false 阻止默认提交
  form.onsubmit = function () {
    return checkUsername() && checkEmail() && checkPwd() && checkRePwd();
  };
</script>
</body>
</html>

要点与易错点:

  • 事件绑定写的是回调函数名,不能加小括号,加了就变成立即调用,失去回调的意义;而表单提交里 checkUsername() 是主动调用,必须加括号。
  • 每个验证函数都要 return true / false,否则提交时 && 拿不到布尔值,无法判断整体是否通过。
  • onsubmit 中 return false 才阻止表单默认提交,return true 或不写返回值都会正常提交。
  • && 有短路特性:前一个函数返回 false,后面的函数就不再执行,所以“一上来就点注册”时只能看到用户名的提示,其余输入框不会显示校验结果——这是这段写法本身的特性。
  • 密码只限制长度时,完全可以用 value.length,不必写正则;正则更适合校验“内容组成”。
  • 邮箱正则里的 . 必须转义成 \.,并且用模式单元 (\.\w+){1,2} 让它整体重复一到两次,否则无法匹配 .com.cn 这种多级后缀。

常见错误 ​

  1. 现象:new RegExp("\\d{6}") 写成 new RegExp("\d{6}") 后匹配异常。
    原因:字符串会先处理转义,\d 到正则里已不是数字类。
    修法:字符串构造正则时写双反斜杠 "\\d{6}",或优先用字面量 / \d{6} /。

  2. 现象:从输入框拿到内容后直接 value.test(str) 报“不是函数”。
    原因:输入框取到的是字符串,不是正则对象。
    修法:先用 new RegExp(value) 转成正则对象,再调用 test/exec。

  3. 现象:replace(/\d/, "0") 只替换了第一个数字。
    原因:没有开启全局模式,replace 默认只替换首个匹配。
    修法:需要全部替换时加 g,如 replace(/\d/g, "0")。

  4. 现象:多行字符串里 /^hello$/ 匹配失败,加 m 后才通过。
    原因:^、$ 默认只匹配整个字符串的开头和结尾。
    修法:按行匹配时使用 m 修饰符;不要误以为 m 会影响其他原子。

  5. 现象:match 加了 g 后拿不到捕获组内容,groups 也不符合预期。
    原因:全局匹配返回整体匹配数组,捕获组/分组信息行为与不带 g 不同。
    修法:需要捕获组细节时去掉 g 或用 exec 逐次提取;只取全部匹配时再用 g。

  6. 现象:表单各输入框失焦时提示都正确,但直接点“注册”仍能提交。
    原因:只做了 blur 校验,没有在 submit 时统一校验并阻止默认行为。
    修法:提交事件中依次调用校验函数,任一失败就 return false 或 preventDefault()。

  7. 现象:事件回调写成 btn.onclick = check(),页面加载就执行,点击反而不执行。
    原因:加了括号是立即调用,不是注册回调。
    修法:注册事件时传函数名或函数体,不加调用括号:btn.onclick = check。

  8. 现象:搜索框输入时请求频繁发出,防抖没有生效。
    原因:每次触发都新建定时器,却没有清除上一次定时器。
    修法:在防抖函数内先 clearTimeout(timer),再 setTimeout 重新计时。

练习 ​

  1. 写一个正则:用户名只能是字母、数字、下划线,长度 6 到 18 位;分别用字面量和 new RegExp 创建,并说明字符串写法要注意什么。

  2. 给定字符串 "hello 123 abc 456",用 test 判断是否包含数字,用 exec 提取第一段连续数字,并比较两者返回值。

  3. 用 replace 把字符串 "a1b2c3" 中所有数字替换为 #;再说明不加 g 时结果会怎样。

  4. 用 split 把 "小明,小红;小刚.小丽" 按逗号、分号、英文句号分割成数组,写出使用的正则。

  5. 实现一个 debounce(fn, delay),用于搜索联想输入;要求连续输入时只在停止输入 delay 毫秒后请求一次,并说明定时器变量应放在哪里。

原视频对照 ​

#视频时长
1模式修饰符04:44
2正则对象的方法18:46
3字符串对象的方法10:21
4模式单元的内容会被单独匹配05:47
5表单验证案例21:53

小结 ​

  1. 正则可分别从“正则对象”和“字符串对象”两侧使用,调用方向不同,参数与返回值也不同。
  2. 纯格式验证优先用 test;需要提取匹配内容时优先考虑 exec/match。
  3. g 影响全局匹配与替换,i 影响大小写,m 影响 ^、$ 的行边界。
  4. 字符串动态构造正则时,反斜杠转义是最容易出错的地方,能写字面量就写字面量。
  5. 捕获组会把分组内容单独带出,但全局匹配时不要想当然依赖捕获组结果。
  6. 表单验证不能只依赖失焦提示,提交时必须统一校验并阻止非法提交。
  7. 防抖的核心是“清除上一次定时器,再重新计时”,适合搜索联想、窗口调整、防重复提交等场景。