
写 JavaScript 这么久正则表达式是我觉得最“玄学”的一块。你说它难吧真正吃透那几十个符号之后很多字符串处理的活儿能瞬间从半小时压缩到一行代码你说它简单吧一碰到复杂匹配[0-9]和\d的区别、贪婪与非贪婪、前瞻后顾……每一个点都能把人绕晕。这篇文章就是要把这些“玄学”拆开用前端开发最常见的表单校验、数据清洗场景把 JavaScript 正则表达式的语法、API、实战套路一次讲清楚。不管你是刚入门的前端新手还是写了两三年业务代码但正则一直靠粘贴复制的同学这篇都能帮你补齐这块短板。1. 正则表达式到底是什么先建立“匹配”的思维模型1.1 正则不是代码是一种“描述规则”的语言很多人第一次接触正则会下意识地把它当成某种“高级代码”来背符号记了一大堆用的时候还是懵。我建议换个角度理解正则表达式本质上不是一段执行逻辑而是一种描述字符串“长相”的规则语言。你写的每个模式都是在告诉 JavaScript我需要找出满足这个形状特征的字符串。打个比方你去图书馆找书管理员问你“找什么书”你说“书名以 Java 开头、中间任意字符、最后是开发指南”管理员就能按这个描述去书架上筛。正则干的就是这件事/^Java.*开发指南$/就是一条更精确、更机器化的描述。区别在于图书馆管理员有理解能力正则是纯字面规则所以你必须在每个细节上都说清楚开头是什么、结尾是什么、中间允许多少字符、字符的取值范围是什么。这也解释了为什么正则初看很难——它和普通命令式编程的“先做 A 再做 B”不太一样它是声明式的你在描述“我要什么样的字符串”而不是“怎么一步步去找”。一旦把思维从“写步骤”切换到“描述形状”后面学语法就容易多了。1.2 字符类与预定义类把“任意字符”说清楚正则最基础的元素是字符类。最直接的写法就是字面量字符/abc/匹配字符串里恰好abc这三个连续字符。但现实中更多需求是“匹配某一类字符”比如“匹配一个数字”这时就需要字符类。方括号[ ]是最直观的字符类写法[0-9]表示任意一个数字[a-zA-Z]表示任意一个英文字母[^0-9]表示任意一个非数字。注意这里的^在方括号开头才表示“非”放在别的位置就是另一个含义了这个细节经常有人踩坑。除了自己写范围JavaScript 还提供了几个预定义类用的人最多也最实用\d等价于[0-9]\D等价于[^0-9]\w等价于[A-Za-z0-9_]注意包含下划线\W等价于[^A-Za-z0-9_]\s匹配空白字符包括空格、Tab、换行\S匹配非空白字符.匹配任意字符但默认不匹配换行符我把这堆符号当成“快捷指令”来记上手会快很多。比如要匹配一个手机号不用写[0-9]这种长串直接\d就行。但要记住\w和\d默认都是基于 ASCII 的\w并不包含中文很多前端新手在匹配中文时发现\w不生效根本原因就在这。后面我会专门讲中文和 Unicode 的处理。1.3 量词与贪婪模式匹配次数怎么控光有字符类还不够你只能匹配“一个”字符。真实场景里手机号是 11 位数字、用户名长度是 3 到 16 位这就需要用量词来控制出现次数。量词总共有这么几个*前一个字符出现 0 次或多次等价于{0,}出现 1 次或多次等价于{1,}?出现 0 次或 1 次等价于{0,1}{n}恰好出现 n 次{n,}至少出现 n 次{n,m}出现 n 到 m 次量词的另一个重要概念是贪婪与非贪婪。默认情况下量词都是贪婪的它会在保证整体匹配成功的前提下尽可能多地匹配字符。举一个经典例子如果用/.*/去匹配b标题/b结果不是匹配到b和/b而是直接吞掉整行因为.*会“贪”到最后一个才停。想让它“见好就收”就在量词后面加一个?变成非贪婪模式/.*?/就能分别匹配出b和/b因为在第一个出现时它就停手了。这个知识点在解析 HTML 标签、配置项提取时几乎必用前端面试也常考一定要搞明白。2. JavaScript 里正则的三种写法和 API 全景2.1 字面量、构造函数与 RegExp 对象前端日常写正则最常见的写法是字面量以斜杠包裹模式后面可选跟修饰符。比如/^\d{11}$/g。字面量写法简单直接性能也好因为它在脚本加载时就被编译了不会每次运行都重新解析。另一种写法是用new RegExp(模式字符串, 修饰符)。这里有个容易翻车的点模式字符串里的反斜杠本身需要转义。比如字面量里写\d表示数字如果放到构造函数里就要写成\\d因为第一个反斜杠是在给字符串字面量转义。很多人直接new RegExp(^\d{11}$)到浏览器里一看模式变成了匹配字母d而不是数字就是因为少了这层转义。动态正则场景才是构造函数的主场。最典型的是搜索关键字用户输入一个词你想把内容里所有出现该词的地方高亮。这时模式里包含用户输入就只能拼接字符串再传给RegExp比如new RegExp(\(${keyword}), g)。但要注意用户输入里如果带了正则特殊字符会破坏整个模式需要先做一个转义函数把.、*、?等字符前面加上反斜杠再拼接。这段经验我在项目里踩过几回没有转义时用户搜a.b结果匹配了一堆奇怪内容页面高亮全乱了。2.2 核心方法test、exec 与字符串方法正则对象和字符串对象之间有几个互相配合的方法新手的困惑点主要是它们分别该用谁。我建议按需求分只想判断“有没有匹配”用regex.test(str)返回布尔值想拿到匹配到的内容、捕获组、位置信息用regex.exec(str)或str.match(regex)要替换用str.replace(regex, replacement)要找位置用str.search(regex)要按正则切分字符串用str.split(regex)其中最容易踩坑的是match。str.match(regex)在不带g修饰符时返回一个数组包含第一个完整匹配、各个捕获组、index和input等属性一旦带上g返回的数组里就只有所有完整匹配的字符串捕获组信息全部丢失。同样的正则、同一个字符串带上g结果结构差别很大这在处理数据时很容易造成 bug。exec则和g绑定得很紧循环调用exec时正则会从上次的lastIndex继续往后找直到返回null。这个特性可以用来遍历提取所有匹配但如果你没有用循环、只在g模式下调用了一次exec它只会返回第一个匹配这跟“全局匹配”的直觉是相悖的。我见过不少同事在这里写错所以统一建议只需要第一处匹配时不加g需要全部结果时要么用match g要么老老实实写exec循环。2.3 修饰符 g、i、m、s、u、y 的含义与坑点修饰符是正则的“开关”JS 里一共有 6 个g、i、m、s、u、y。g是全局匹配i是忽略大小写这两个没啥可说的。m是多行模式默认情况下^和$匹配的是整个字符串的开头和结尾加了m之后它们会匹配每一行的行首行尾。比如处理多行文本里以 TODO 开头的行/^TODO/gm就能把每一行开头的 TODO 都抓到。s修饰符是 ES2018 才加的只干一件事让.能匹配换行符。在它出现之前想匹配“任意字符包括换行”只能写成[\s\S]这种别扭写法。所以现在看到[\s\S]*?的老代码心里要有数那是在模拟s模式。u是 Unicode 模式处理中文、emoji 时很重要。加了u之后正则可以用\u{1F600}这类形式直接匹配码点也能正确处理四字节的 emoji 字符否则\w和.在 emoji 面前会出问题。这个后面展开讲。y是粘性匹配比较少见。它和g类似依赖lastIndex但区别是y要求匹配必须从lastIndex位置开始不能跳过中间字符。做词法分析、解析器时会用到业务开发基本用不上了解一下即可。3. 正则语法拆解分组、捕获与零宽断言3.1 分组与捕获用小括号圈出关键信息小括号在正则里有两种作用分组和捕获。分组就是把多个字符当做一个整体来应用量词比如/(ab)/匹配ab、abab、ababab。捕获则是把匹配到的子内容存下来方便后续引用。捕获组从 1 开始编号在replace里用$1、$2引用在正则内部用\1、\2反向引用。举个例子想匹配重复出现的单词比如“hello hello”可以用/(\w)\s\1/这里的\1表示“和第一个捕获组一样的内容”。反向引用是处理重复模式的利器比如匹配成对标签、引号内的内容。如果只是想把几个字符圈起来应用量词但不想把内容存下来就用非捕获组(?:...)。这样既能分组又不会多出捕获组编号对exec的结果结构也更干净。命名捕获组(?name...)是 ES2018 的新特性可以给捕获组起名字在replace里用$name引用在exec结果里通过groups.name取值。我强烈建议复杂正则里优先用命名捕获组否则$1、$2多了真的会看花眼。3.2 前瞻与后顾零宽断言都在判断“后面/前面是什么”前瞻和后顾是正则里最有意思也最容易被忽略的能力它们统称零宽断言。零宽的意思是它们本身不消费字符只做位置的“检查”。就像过关卡时不拿东西只是递给保安看一眼证件然后继续往前走。JS 支持四种断言(?...)正向前瞻右边必须匹配...(?!...)负向前瞻右边必须不匹配...(?...)正向后顾左边必须匹配...(?!...)负向后顾左边必须不匹配...以数字千分位格式化为例要给1234567.89加上千分位逗号核心正则就可以写成/\B(?(\d{3})(?!\d))/g。这里的\B是“非单词边界”(?(\d{3})(?!\d))表示“当前位置后面必须是一组组三位数字且之后不能再跟数字”。只看一遍很难懂但拆开就清楚了它是在找所有“应该插入逗号的位置”。这就是零宽断言的威力位置检查配合捕获组和全局匹配批量处理起来非常方便。前瞻在 JS 里一直支持得很好后顾则是 ES2018 才加入的。现在主流浏览器都支持了但如果你要兼容很老的环境还是得避开后顾。密码强度校验也是前端面试常客要求同时包含大写、小写、数字和特殊字符长度 8 位以上用前瞻写出来很优雅但要注意多个断言之间是独立的它们都在检查同一个位置所以顺序无关紧要。3.3 常用正则套路与边界细节这一节我整理了实际开发中反复出现的几个套路掌握了能省不少事。第一个是边界符号的运用。^和$分别表示字符串开头和结尾做“完全匹配”校验时一定要带上比如表单校验手机号写/^\d{11}$/少了^和$就会误放行abc13800138000def这样的字符串因为正则默认只做部分匹配。很多人校验不严bug 源头就在这。第二个是\b单词边界。\b匹配的是“单词字符\w和非单词字符之间的位置”。比如想匹配独立的 cat而不想匹配 category 里的 cat就写/\bcat\b/。注意\b对中文不友好中文和中文之间不会形成单词边界所以处理中文文本时经常要绕开它。第三个是特殊字符的转义。需要匹配.、*、?、、^、$、[、]、{、}、(、)、|、\这些符号时都要在前面加反斜杠。比如匹配文件后缀/\.js$/这里的点如果不转义就会匹配任意字符/a.js/也会匹配aXjs之类的内容。转义是最容易忽略的细节尤其在拼接动态正则时一定要做统一转义。这三个套路看着简单实际项目里能避免一大半低级 bug。4. 实战案例从表单校验到数据清洗4.1 手机号校验边界条件与最新号段前端对手机号的校验核心诉求是“格式是否正确”。目前国内的手机号是 11 位以 1 开头第二位通常是 3、4、5、6、7、8、9后面跟 9 位任意数字。一个够用的正则可以写/^1[3-9]\d{9}$/有人会纠结要不要把每个号段都列全。我的建议是前端做到这个精度就足够了号段更新很快把校验写得太死反而会误伤新号段用户。如果业务上实在需要细分后端可以再做更精确的校验前端没必要把规则锁死。实现时还有两个细节。一是使用 HTML 的 input 属性做初步限制比如maxlength11、typetel配合 input 事件过滤非数字字符体验会好很多。二是校验时机不要每次按键都弹错误比较友好的做法是输入框失焦时校验或者点击提交时统一校验。实时校验不是不行但要注意给用户足够的缓冲。4.2 邮箱校验实用版与严谨版的取舍邮箱正则堪称前端“最容易被抄错”的案例。网上流传的所谓完美邮箱正则又长又复杂实际用起来还可能把合法邮箱拒之门外。我在生产环境里常用的实用版是/^[\w.-][\w-](\.[\w-])$/这个版本能处理nameexample.com、nametagexample.com、a.b_cexample.co.uk之类常见格式基本够用。注意[\w.-]里把点、加号、横杠都纳入了用户名常见字符后面是域名部分用(\.[\w-])保证至少有一个点号后缀。严谨到什么程度才算好我的建议是按业务风险来。如果是注册流程的关键步骤宁可放宽一点再把激活邮件发出去让用户点链接确认这比前端正则拦死一个合法地址好得多。邮箱格式本身很复杂域名可以是有特殊字符的国际化域名正则想要 100% 正确几乎不可能所以“够用 后端兜底”才是工程上的合理选择。4.3 车牌号校验普通燃油车与新能源车车牌号校验在停车场管理、车辆登记类页面很常见。普通车牌的结构是省份简称1 位汉字 发牌机关代号1 位字母 序号5 位字母和数字混合但一般不能有 I 和 O。新能源车牌是 8 位比普通车牌多一位是汉字 字母 6 位序号且最后一位必须是字母D 或 F 加一位数字的情况也有实际规则更细不同地区有差异。一个业务上常用的校验正则如下// 普通燃油车 /^[\u4e00-\u9fa5][A-Z][A-HJ-NP-Z0-9]{5}$/ // 新能源车 /^[\u4e00-\u9fa5][A-Z][A-HJ-NP-Z0-9]{6}$/这里的[\u4e00-\u9fa5]是常用中文字符范围[A-HJ-NP-Z]是排除了 I 和 O 的字母集合因为这两个字母和数字 1、0 太容易混淆。实际做页面时我一般还允许用户输入的字母统一转大写再校验避免大小写混着比对出问题。上面提到的中文字符范围我建议直接用码点范围而不是个别字库这样覆盖面更稳。当然如果业务需要严格按最新的车牌编码规则走还是以后端或公安接口的校验为准前端主要解决“看起来像不像”的问题。4.4 数据清洗实战提取、脱敏与格式化正则除了校验第二大用途是对已有字符串做清洗。提取所有数字用/[^0-9]/g去替换或者用/\d/g去 match。比如从一串包含金额、编号的文本里抽出所有数字const nums str.match(/\d/g)。手机号脱敏是另一个高频需求。把13800138000变成138****8000正则一行搞定str.replace(/^(\d{3})\d{4}(\d{4})$/, $1****$2)这里的$1、$2引用的是两个捕获组也就是前三位和最后四位中间四位被星号替代。注意要确保输入恰好是 11 位否则这个正则不会匹配也就不会脱敏。千分位格式化也可以顺手做掉str.replace(/\B(?(\d{3})(?!\d))/g, ,)。这个正则我在上一节拆过这里直接拿来用。使用前最好先确认字符串是纯数字或者数字部分在前否则小数点和负号的位置会被影响。清洗类正则的通用建议先明确边界再动手写。比如“提取所有数字”数字之间如果夹杂中文、逗号、小数点结果是否符合预期先想清楚再操作免得清洗完还要人工返工。4.5 身份证校验长度与格式之外的问题热词里总有人问身份证号码怎么用正则校验这里补充一下。大陆身份证号码是 18 位前 6 位地区码中间 8 位出生日期最后 4 位包含顺序码和校验码校验码可能有X。一个常用的格式校验正则/^\d{6}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/这个正则保证了地区是 6 位数字、年份前缀是 18/19/20、月份在 01-12、日期在 01-31、最后三位数字加一位数字或 X。但它无法校验 2 月是否有 30 号也无法校验最后一位校验码是否真的正确。真正严格的做法是单独解析出生日期、调用校验码算法这两个步骤一般放在后端或专门的工具函数里。前端拿这个正则做第一道过滤足够了能把明显错误的格式挡在门外。如果想更进一步可以在前端把第 17 位乘以对应权重、累加后取模算出校验码和最后一位比对这套算法网上有现成实现复制前记得跑通测试用例。5. 常见问题与排查技巧实录5.1 匹配结果和预期不符先检查这几处正则写出来不生效我在社区答疑时遇到最多的是下面几种情况。一是没带头尾锚点。前面说过/138/能匹配13800138000也可能匹配其他含 138 的字符串如果你想做完整匹配^和$不能省。二是match带不带g。同一个正则带g返回纯匹配列表不带g返回第一个匹配加捕获组结构完全不同。发现结果里出现index、input这样的字段说明没带g发现多出来一堆捕获组的值可能是分组太多。三是字符串里的反斜杠问题。new RegExp(\\d)和new RegExp(\d)是完全不同的后者在字符串里\d会被解释成d。写动态正则时先console.log一下你拼接出来的字符串确认反斜杠是否正确能省很多调试时间。四是大小写问题。i修饰符没加导致用户输入House时匹配不到house。这种问题定位最快先看修饰符再看模式。提示遇到匹配结果不对先别急着改正则把当前正则、目标字符串和实际输出都打印出来肉眼对比一遍90% 的问题能当场发现。5.2 灾难性回溯正则把页面搞卡死正则导致页面卡死并不是玩笑。某些正则结构在匹配失败时会导致灾难性回溯指数级消耗 CPU 时间。经典罪魁祸首是嵌套量词比如/(a)b/匹配一串全是 a 却没有 b 的字符串时引擎会不断尝试各种分组方式直到超时。业务代码里解析复杂文本时我见过太多类似的写法比如(.*)*、(?:[^,],)*之类的嵌套。排查方法优先看模式里有没有“量词套量词”的结构。如果确实需要匹配复杂的嵌套结构比如 HTML建议不要硬用正则改用专门解析器需要匹配宽松内容时用非贪婪模式 明确字符类比如[^\n]*能大幅降低回溯风险。JavaScript 目前还没有原子组所以没法用(?)直接切断回溯。一个替代思路是用前瞻模拟把嵌套部分改写成确定性的结构或者对输入长度先做检查。这个属于进阶话题但前端性能排查遇到正则卡顿至少要知道方向。5.3 中文和 Unicode\w 认不出汉字JS 默认的\w、\d都是基于 ASCII 的\w不匹配汉字。很多新手写正则匹配中文昵称时写/^\w$/校验结果中文全部不通过就是这个原因。处理中文最核心的工具是 Unicode 码点范围。比如匹配中文字符可以用[\u4e00-\u9fa5]匹配中英文数字混搭可以用/^[\u4e00-\u9fa5A-Za-z0-9]$/。加了u修饰符后还可以用更精确的 Unicode 属性转义/\p{ScriptHan}/u不过这种写法对浏览器的兼容性要求更高生产环境要看着点使用。emoji 也是同样的道理。一个 emoji 可能占 2 个码点没有u修饰符时.和[\s\S]会把 emoji 劈成两半。处理包含 emoji 的用户内容时尽量带上u修饰符用/\p{Emoji}/u之类来判断性能上也会有帮助。5.4 调试工具与可读性习惯最后分享几个我日常用得很顺的技巧。第一线上正则调试工具一定用起来。regex101.com 这类工具能实时高亮匹配位置、解释每个符号的含义、显示捕获组和步骤消耗排查复杂正则效率很高。在工具里确认没问题再复制回代码里。第二正则也讲究可读性。用命名捕获组替代一堆$1、$2用(?:)去掉无关捕获组同一个正则拆成几个小的中间变量分别起好名字。比如先判断手机号格式再判断号段最后判断运营商每一步的意图都清楚后续维护的人会感谢你。第三复杂正则一定要写注释。可以把模式拆成多段说明把“为什么要这么写”留下来。代码评审时看到一堆乱码一样的正则真的很劝退稍微加两句注释观感完全不同。我个人是“少背多看、多写多测”的拥趸。正则符号虽然多但常用组合真的就那几十个。遇到新场景先想清楚输入和输出再去工具里写一点点验证比硬背全语法效率高得多。这几年做前端正则帮我处理过配置解析、日志清洗、表单校验、路由匹配……几乎每一次用回都值得。这篇把 JavaScript 正则的基础框架、API 用法和实战坑点都过了一遍你把它当成自己的学习手册常翻常新就好。