一个前端正则校验引发的问题
最近在一个项目上线的时候遇到一个奇葩的问题,用户登录使用邮箱登录,邮箱前端有合法正则校验,快要发布上线时,测试提到一个bug,在暴力测试邮箱输入框内快速输入字符,前端校验提示报错,到了一定长度导致页面卡死。期初怀疑是因为快速输入导致双向绑定卡顿,但是又一想不可能呀,这中双向绑定在vue下早就实现了,怎么可能又这个问题。
尝试取消双向绑定,然后使用@input事件配合debounce去实现,但是发现快速输入依然卡顿。
接着尝试去掉校验,没有问题,加上校验就有问题,所以就开始排查正则校验规则,结果还真实正则校验的问题。
1 正则校验的问题
在表单校验时,不可使用过度消耗配置的正则表达式,由于正则校验消耗,导致页面卡顿。
来看我们原来的校验规则:
/^(\w+[-|\.]?)+\w@(\w+(-\w+)*\.)+[a-zA-Z]{2,}$/
我们来看这个正则的问题。
1.1 回溯问题
用户名部分 (\w+[-|\.]?)+
内层 \w+ 和外层 + 形成嵌套量词结构,如 (a+)+
当输入结尾字符不匹配时,如 user-@domain.com,引擎会尝试所有可能的 \w+ 和分隔符组合,回溯路径呈指数增长
域名部分 (\w+(-\w+)*\.)+
双重嵌套 (\w+ (-\w+)* )+ 导致回溯爆炸,尤其当域名含多个连字符时(如 @sub--domain.com)
1.2 冗余捕获组与歧义语法
捕获组 (...) 会存储匹配内容,而此处无需捕获子匹配,造成额外开销。
字符类 [-|\.]? 中的 | 被误解为字面竖线(实际应为 [.-]),增加匹配复杂度
1.3 回溯攻击敏感结构
对无效输入(如 a@b.c 或 name@-invalid.com)的匹配尝试会触发悲观回溯,引擎需穷举所有失败路径才能返回 false
1.4 性能影响示例
假设输入邮箱: **"ab@x"
**(故意缩短域名部分)
引擎执行过程:
- 用户名部分尝试
(\w+[-|\.]?)+
的所有分割组合:
"a"
、"a-"
、"a."
、"ab"
、"ab-"
...(指数级尝试) - 域名部分因
[a-zA-Z]{2,}
要求至少2个字母,但"x"
长度不足,再次触发回溯。
结果:单次验证可能产生 **>10,000 次回溯**,阻塞 JS 主线程
看到这个示例,是不是感觉这可能就是一个“惨案”呢。
2 正确的正则
写一个正确的正则,提高效率,减少在用户快速输入过程中程序消耗。
2.1 消除回溯
// 优化后正则(零回溯,性能提升5倍+)
/^\w+(?:[.-]\w+)*@(?:\w+-)*\w+\.[a-zA-Z]{2,}$/;
2.1.1 重构嵌套量词
\w+(?:[.-]\w+)* 替代 (\w+[-|\.]?)+
非捕获组 (?:) 消除存储开销,明确分隔符逻辑:[.-] 匹配 . 或 -(无歧义)
(?:\w+-)*\w+\. 替代 (\w+(-\w+)*\.)+
合并相邻 \w+,避免空子匹配(如 @-domain.com 直接失败)
/^[\w.+-]+@(?:\w+(?:-\w+)*\.)+[a-zA-Z]{2,}$/
2.1.2 锚点精准控制
保留 ^ 和 $ 确保快速失败,避免部分匹配尝试
2.1.3 域名简化匹配
\.[a-zA-Z]{2,}$ 精确匹配顶级域名,减少冗余检查
3 优化后的正则
/^\w+(?:[.-]\w+)*@(?:\w+-)*\w+\.[a-zA-Z]{2,}$/
优化后性能对比
由以上解释,明白了这其中正则配置不合理导致的性能消耗和最后页面卡顿的罪魁祸首。当然为了提高前端正则校验的效率可能会放弃某些严格场合,所以还要配合后端,在整体提交时,后端再做一次校验。