前端新人必看:JavaScript正则表达式从入门到实战(附避坑指南)

在这里插入图片描述

前端新人必看:JavaScript正则表达式从入门到实战(附避坑指南)

前端新人必看:JavaScript正则表达式从入门到实战(附避坑指南)

引言——为什么正则表达式总让人又爱又恨?

我第一次在代码里写下 /^[a-z]+$/i 的时候,内心是崩溃的:这串像猫踩键盘蹦出来的符号,居然就能拦住用户输入“!@#$%”?可当我发现一行 .replace(/(\d{4})(\d{4})(\d{4})(\d{4})/g,'$1 **** **** $4') 就能把银行卡号瞬间脱敏,瞬间又原地复活。正则就像前端界的香菜——有人闻到就皱眉,有人顿顿离不了。它能在三行代码里干掉你原本要写三十行的字符串校验,也能在凌晨三点因为一个回溯爆炸把你 CPU 飙到 100%。今天这篇,把我十年踩过的坑、哭过的泪、偷过的懒,全部打包成一篇“人话版”攻略,带你从“这是啥”到“这题我会”,再到“这坑我熟”。


正则表达式到底是什么——把“找规律”翻译成“写公式”

别被“正则”这俩字吓到,它本质上就是“用文字描述文字”的模板。想象你在公司群@人:

“请所有姓‘张’、名字两个字的小伙伴来领月饼。”

这句话翻译成正则,就是 /^张[\u4e00-\u9fa5]{2}$/。拆开看:

  • ^ —— 开头,表示“从姓开始”
  • —— 字面量,必须匹配“张”
  • [\u4e00-\u9fa5]{2} —— 任意两个汉字(Unicode 范围)
  • $ —— 结尾,表示“名字只能两个字”

你看,正则只是把“口语规则”换成“符号规则”。学会这门“符号口语”后,你就能让浏览器帮你“一目十行”地找规律,而不是人肉 for 循环里写一堆 if


JavaScript 中创建正则的两种姿势——字面量爽一时,构造函数保一世

1. 字面量写法——顺手拈来

// 校验十六进制颜色 #ff0012
const hex = /^#[0-9a-f]{6}$/i;
hex.test('#BADA55'); // true

优点:

  • 写在斜杠里,像写字符串一样直观
  • 性能略高(引擎提前编译)

缺点:

  • 不能插变量!下面这种写法会原地爆炸
const color = 'red';
const reg = /^#color$/; // 想匹配 "#red"?别闹,它字面量就是 "#color"

2. 构造函数写法——动态拼接必备

// 用户输入的关键词高亮
function buildHighlightRegex(keyword) {
  // 别忘了转义用户输入,否则“.”会被当成元字符
  const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  return new RegExp(`(${escaped})`, 'gi');
}

const reg = buildHighlightRegex('(C++)');
'<title>C++ primer</title>'.replace(reg, '<mark>$1</mark>');
// "<mark>(C++)</mark> primer"

优点:

  • 字符串模板里随便塞变量,动态构建真香
  • 可以复用用户输入、后端配置

缺点:

  • 写两层转义写到怀疑人生:字符串里先写一次“\”,RegExp 里再读一次“\”
  • 运行时才编译,短生命周期场景性能略差

小结:静态规则用字面量,运行时规则用构造函数;别在循环里 new RegExp 一百万次,浏览器会喊你爸爸。


核心语法全解析——把“正则符号表”换成“人话说明书”

元字符——“不是你以为的那个意思”

符号人话解释代码示例
.除了换行啥都行/a.c/.test('a\nc') // false
\ddigit = 数字/\d{11}/.test('13800138000') // true
\wword = 字母数字下划线/\w+/.test('var_1') // true
\sspace = 空白/a\sb/.test('a b') // true

量词——“几次”

符号人话解释代码示例
{n,m}最少 n 次,最多 m 次/\d{3,5}/.exec('12345')[0] // "12345"
?0 或 1 次,等价 {0,1}/colou?r/i.test('color') // true
+至少 1 次,等价 {1,}/go+gle/i.test('goooogle') // true
*任意次,包括 0/<[^>]*>/.exec('<div>hi</div>')[0] // "<div>"

边界符——“别串戏”

// 匹配“web”单词,而不是“微博”里的‘wb’
const wordWeb = /\bweb\b/i;
wordWeb.test('web前端'); // true
wordWeb.test('微博');    // false

分组 + 反向引用——“自己抓到的自己用”

// 把 yyyy-mm-dd 换成 mm/dd/yyyy
const date = '2025-01-07';
const us = date.replace(/(\d{4})-(\d{2})-(\d{2})/, '$2/$3/$1');
console.log(us); // "01/07/2025"

分组 $1$2$3 就是“反向引用”,把左边括号按出现顺序编号。
进阶:命名分组(ES2018)——再也不数 $1$2$3 数到眼瞎:

const named = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const m = '2025-01-07'.match(named);
console.log(m.groups.year);  // "2025"

RegExp 对象隐藏技能——别只会 .test

1. test——“是或不是”

const mobile = /^1[3-9]\d{9}$/;
mobile.test('13800138000'); // true

注意:全局标志 g 会捣乱——lastIndex 会记忆状态,连续调用容易翻车:

const re = /a/g;
console.log(re.test('a')); // true
console.log(re.test('a')); // false  ???
// 解决:别带 g,或者手动 re.lastIndex = 0

2. exec——“给我详情”

const re = /(?<proto>https?):\/\/(?<domain>[^/?#]+)/g;
let m;
while ((m = re.exec('https://kimi.com http://baidu.com')) !== null) {
  console.log(m.groups.domain); // kimi.com  baidu.com
}

3. String 家族方法——“字符串也能调”

方法场景示例
match一次性抓所有'a1b2c3'.match(/\d/g) // ["1","2","3"]
replace替换、模板见下文“日志脱敏”
split按正则劈开`'1,2

4. 回调式 replace——“替换也能玩花活”

// 把下划线命名改成小驼峰
const snake = 'user_name_and_age';
const camel = snake.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
console.log(camel); // "userNameAndAge"

修饰符不只是可选项——“小字母,大差别”

修饰符含义实战场景
g全局找出所有匹配,而不是第一个就停
i忽略大小写用户验证码输入不区分大小写
m多行^ $ 可以匹配行首行尾,而不是整个字符串首尾
sdotAll. 匹配换行符,采集 HTML 块时爽翻
uUnicode匹配 emoji、生僻字必备
ysticky粘连匹配,从 lastIndex 开始,做词法分析神器

组合技案例:

// 把 markdown 的每一级标题提取出来
const md = `# ES2025 新特性\n## 正则扩展\n### 命名分组`;
const heading = /^([#]+)\s+(.+)$/gim;
let h;
while ((h = heading.exec(md)) !== null) {
  console.log(`${h[1].length} 级标题:${h[2]}`);
}
// 1 级标题:ES2025 新特性
// 2 级标题:正则扩展
// 3 级标题:命名分组

正则性能那些事儿——“贪婪与懒惰的拉锯战”

1. 贪婪 vs 懒惰

// 想匹配第一个 </div>
const html = '<div><div>hi</div></div>';
html.match(/<div>.*<\/div>/);   // 贪婪:整串都吃掉
html.match(/<div>.*?<\/div>/);  // 懒惰:只到第一个 </div>

懒惰用 *?+?,但别高兴太早:懒惰只是“回溯得少”,不是“零开销”。

2. 回溯爆炸——“一个(.*?)当场去世”

来看核弹:

const re = /(a+)+b/;
re.test('aaaaaaaaaaaaaaaaac'); // 几十毫秒变几秒

原因:(a+)+ 让引擎陷入“我该在哪一层分组结束”的哲学思考,指数级回溯。
解决:

  • 具体化字符集,别写太宽泛的 .\s\S
  • 使用“独占模式”——JavaScript 目前不支持 (?>...),但可以用“反向否定预查”曲线救国:
const atomic = /(?=(a+))\1b/; // 先捕获,再引用,等价独占

3. 提前编译 & 缓存

// 全局缓存正则,避免在 React render 里疯狂 new
const REGEX_CACHE = new Map();
function getRegex(pattern, flags) {
  const key = `${pattern}_${flags}`;
  if (!REGEX_CACHE.has(key)) {
    REGEX_CACHE.set(key, new RegExp(pattern, flags));
  }
  return REGEX_CACHE.get(key);
}

表单验证中的经典用法——“一行正则行天下?别冲动”

1. 手机号

const mobile = /^1[3-9]\d{9}$/;

说明:

  • 第二位 3-9:随运营商新号段更新,把 199/198/195 都囊括
  • 全 11 位:拒绝 012、106 开头

2. 邮箱——“能跑就行” vs “RFC5322 完全体”

// 80% 场景够用:
const email = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
// 100% 场景请放弃正则,用解析库或浏览器 `<input type="email>` 原生校验

原因:邮箱的注释、引号、IPv6 域名能把正则写到 6KB,别为难自己。

3. 身份证——“18 位还带校验码”

function validateId(id) {
  const reg = /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dX]$/i;
  if (!reg.test(id)) return false;
  // 校验码
  const weights = [7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2];
  const checksums = '10X98765432';
  let sum = 0;
  for (let i = 0; i < 17; i++) sum += id[i] * weights[i];
  return checksums[sum % 11] === id[17].toUpperCase();
}

教训:别光写“看起来像”,校验码算法能挡住 90% 的假号。

4. 密码强度——“一行正则不现实”

// 至少 8 位,同时含大小写、数字、特殊字符
const strong = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*])[A-Za-z\d!@#$%^&*]{8,}$/;

解释:

  • 用“零宽正向预查”(?=.*[a-z]) 分别检查四类字符
  • 不含空格,避免用户复制粘贴带空白
  • 但别迷信:长口令“correct horse battery staple”比 “P@ssw0rd” 更安全,考虑用“熵值估算”替代“复杂字符”。

文本处理实战技巧——“日志、敏感词、模板,一把梭”

1. 日志脱敏——“一行代码过等保”

// 把手机号、银行卡、身份证全打码
function desensitize(log) {
  return log
    .replace(/\b1[3-9]\d{5}(\d{4})\b/g, '****$1')        // 手机
    .replace(/\b(\d{4})\d{8}(\d{4})\b/g, '$1****$2')     // 银行卡
    .replace(/\b(\d{6})\d{8}(\w{4})\b/g, '$1****$2');    // 身份证
}
console.log(desensitize('login user=13800138000, id=110105199003078888'));
// "login user=****13800, id=110105****8888"

2. 敏感词过滤——“DFA 太慢?先上正则挡一波”

const keywords = ['996','icu','加班'];
const escape = w => w.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(keywords.map(escape).join('|'), 'gi');
function filter(text) {
  return text.replace(regex, m => '*'.repeat(m.length));
}
console.log(filter('今天又要996,进了icu')); // "今天又要***,进了***"

注意:正则不擅长“跳脱特殊符号”+“多音字”+“嵌套关键词”,高压场景请上 DFA + 人工审核。

3. 模板引擎——“自己撸一个 mustache”

function tinyRender(tpl, data) {
  return tpl.replace(/\{\{(\w+)\}\}/g, (_, key) => data[key] ?? '');
}
const html = tinyRender('Hello {{name}}, order No.{{id}}', {name:'李雷',id:'666'});
console.log(html); // "Hello 李雷, order No.666"

遇到“不匹配”怎么办——“调试正则的六脉神剑”

  1. console.log 不够用?先打印 .source
console.log(`当前正则是:/${regex.source}/${regex.flags}`);
  1. String.match 看返回 null 还是空数组,分清“没匹配”与“匹配了空串”
  2. g 标志先摘掉,避免 lastIndex 干扰
  3. regexr.com(线上工具,非广告,纯友情)里开“解释模式”,一行行高亮
  4. 把目标字符串拆短,二分定位:一半能匹配,一半不能,快速找到“断点字符”
  5. 终极杀器:把正则拆成“注释模式”——
const re = new RegExp(`
  ^                  # 行首
  (?=.*[a-z])        # 至少一个小写
  (?=.*[A-Z])        # 至少一个大写
  .{8,}              # 长度 8+
  $                  # 行尾
`.replace(/\s+#.*|\s+/g,''));  // 压缩后生产环境再用

写出可维护正则的秘诀——“命名、注释、模块化”

1. 命名分组 + 注释

const DATE_REGEX = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const {year, month, day} = '2025-01-07'.match(DATE_REGEX).groups;

2. 把正则当“配置”抽离

// regex-config.js
export const REGEX = {
  mobile: /^1[3-9]\d{9}$/,
  email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
  // 集中写注释,方便同步给后端
};

3. 单元测试覆盖

import {REGEX} from './regex-config';
test('mobile', () => {
  expect(REGEX.mobile.test('13800138000')).toBe(true);
  expect(REGEX.mobile.test('12800138000')).toBe(false); // 第二位 2 不合法
});

那些年踩过的正则大坑——“血与泪的 FAQ”

坑位场景现场翻车正确姿势
UTF-8 编码emoji 分割/./g 把“😂”切成两个乱码u 修饰符:/./gu
特殊字符用户输入“C++”直接 new RegExp('C++') 报错先转义:`user.replace(/[.*+?^${}()
跨浏览器IE11 不支持 lookbehind(?<=@)\w+ 直接 SyntaxError用捕获组+replace 模拟,或上 babel-plugin-transform-modern-regex
贪婪回溯超长空格/\s+\s+/g 匹配 10KB 空格卡死量词细化,或换非正则(字符串 split)

让正则更聪明的小技巧——“和现代 JS 打成一片”

1. 动态构造 + 数组方法

// 生成“匹配任意一项”的正则
const langs = ['js','ts','jsx','tsx'];
const reg = new RegExp(`\\.(${langs.join('|')})$`, 'i');
console.log(reg.test('index.tsx')); // true

2. 与 Intl.Collator 搭配做“模糊拼音”

// 把拼音相同字符归一类,再构造正则
const pinyinMap = {
  'a': '[aāáǎà]', 'e': '[eēéěè]', 'i': '[iīíǐì]'
};
function buildPinyinRegex(keyword) {
  const escaped = [...keyword].map(ch => pinyinMap[ch]||ch).join('');
  return new RegExp(escaped, 'i');
}

3. 结合 Proxy 做“可链式”验证器

const Validator = new Proxy({}, {
  get(_, prop) {
    return (regex) => (value) => ({ pass: regex.test(value), prop, value });
  }
});
const check = Validator.mobile(/^1[3-9]\d{9}$/);
console.log(check('13800138000')); // {pass: true, prop: 'mobile', value: '13800138000'}

结语——“正则不是银弹,却是瑞士军刀”

写正则就像做菜:盐放少了没味道,放多了齁嗓子;但掌握了火候,你就能把“字符串”这盘菜炒出花来。别指望一篇万字长文就让你变成“正则大师”,真正的高手都是在凌晨三点和 CPU 100% 的拉锯战里淬炼出来的。把这篇文章收藏起来,下次遇到“为啥不匹配”的时候,翻一翻、调一调、笑一笑,然后——

const life = /bug/g;
while (life.test('life')) {
  console.log('fix one more');
}

祝你写最少的代码,匹配最多的需求,我们评论区或者 StackOverflow 见。

欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。

推荐:DTcode7的博客首页。
一个做过前端开发的产品经理,经历过睿智产品的折磨导致脱发之后,励志要翻身农奴把歌唱,一边打入敌人内部一边持续提升自己,为我们广大开发同胞谋福祉,坚决抵制睿智产品折磨我们码农兄弟!


专栏系列(点击解锁)学习路线(点击解锁)知识定位
《微信小程序相关博客》 持续更新中~结合微信官方原生框架、uniapp等小程序框架,记录请求、封装、tabbar、UI组件的学习记录和使用技巧等
《AIGC相关博客》 持续更新中~AIGC、AI生产力工具的介绍,例如stable diffusion这种的AI绘画工具安装、使用、技巧等总结
《HTML网站开发相关》 《前端基础入门三大核心之html相关博客》前端基础入门三大核心之html板块的内容,入坑前端或者辅助学习的必看知识
《前端基础入门三大核心之JS相关博客》前端JS是JavaScript语言在网页开发中的应用,负责实现交互效果和动态内容。它与HTML和CSS并称前端三剑客,共同构建用户界面。
通过操作DOM元素、响应事件、发起网络请求等,JS使页面能够响应用户行为,实现数据动态展示和页面流畅跳转,是现代Web开发的核心
《前端基础入门三大核心之CSS相关博客》介绍前端开发中遇到的CSS疑问和各种奇妙的CSS语法,同时收集精美的CSS效果代码,用来丰富你的web网页
《canvas绘图相关博客》Canvas是HTML5中用于绘制图形的元素,通过JavaScript及其提供的绘图API,开发者可以在网页上绘制出各种复杂的图形、动画和图像效果。Canvas提供了高度的灵活性和控制力,使得前端绘图技术更加丰富和多样化
《Vue实战相关博客》持续更新中~详细总结了常用UI库elementUI的使用技巧以及Vue的学习之旅
《python相关博客》持续更新中~Python,简洁易学的编程语言,强大到足以应对各种应用场景,是编程新手的理想选择,也是专业人士的得力工具
《sql数据库相关博客》持续更新中~SQL数据库:高效管理数据的利器,学会SQL,轻松驾驭结构化数据,解锁数据分析与挖掘的无限可能
《算法系列相关博客》持续更新中~算法与数据结构学习总结,通过JS来编写处理复杂有趣的算法问题,提升你的技术思维
《IT信息技术相关博客》持续更新中~作为信息化人员所需要掌握的底层技术,涉及软件开发、网络建设、系统维护等领域的知识
《信息化人员基础技能知识相关博客》无论你是开发、产品、实施、经理,只要是从事信息化相关行业的人员,都应该掌握这些信息化的基础知识,可以不精通但是一定要了解,避免日常工作中贻笑大方
《信息化技能面试宝典相关博客》涉及信息化相关工作基础知识和面试技巧,提升自我能力与面试通过率,扩展知识面
《前端开发习惯与小技巧相关博客》持续更新中~罗列常用的开发工具使用技巧,如 Vscode快捷键操作、Git、CMD、游览器控制台等
《photoshop相关博客》 持续更新中~基础的PS学习记录,含括PPI与DPI、物理像素dp、逻辑像素dip、矢量图和位图以及帧动画等的学习总结
日常开发&办公&生产【实用工具】分享相关博客》持续更新中~分享介绍各种开发中、工作中、个人生产以及学习上的工具,丰富阅历,给大家提供处理事情的更多角度,学习了解更多的便利工具,如Fiddler抓包、办公快捷键、虚拟机VMware等工具

吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤

非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DTcode7

客官,赏个铜板吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值