Javascript 中可以通过以下两种方式写正则:
- 正则表达式字面量
- 通过构造函数 RegExp 的实例
例如,创建一个正则用于精确匹配字符串 ‘test’。
let regExp = /test/
let regExp = new RegExp('test')
// 或者
let regExp = new RegExp(/test/)
常见的修饰符如下:
模式 | 说明 |
---|---|
g | global 简写,全局匹配,找到所有满足匹配的子串,而不是默认只匹配首次结果。 |
i | ignore case 简写,匹配过程中,忽略英文字母大小写,如 /test/i 可以匹配 Test、teSt、TesT 等。 |
m | multiline 简写,多行匹配,把 ^ 和 $ 变成行开头和行结尾。比如可以匹配文本域(textarea)元素中的值。 |
u | unicode 简写,允许使用 Unicode 点转义符。 |
y | sticky 简写,开启粘连匹配,正则表达式执行粘连匹配时试图从最后一个匹配位置开始。 |
RegExp 相关实例方法:
模式 | 说明 |
---|---|
1,…,1,…,1,…,9 | 最近一次第 1-9 个分组捕获的数据。 |
input | 最近一次目标字符串,可以简写成 $_ 。 |
lastMatch | 最近一次匹配的文本,可以简写成 $& 。 |
lastParen | 最近一次捕获的文本,可以简写成 $+ 。 |
leftContext | 目标字符串中 lastMatch 之前的文本,可以简写成 $` 。 |
rightContext | 目标字符串中 lastMatch 之后的文本,可以简写成 $’ 。 |
匹配字符 | |
模式 | 说明 |
– | – |
字母、数字 | 匹配字符本身。比如/javascript/,匹配 “javascript”;/123/,匹配 “123”。 |
\0 | 匹配 NUL 字符。 |
\t | 匹配水平制表符 |
\v | 匹配垂直制表符。 |
\n | 匹配换行符。 |
\r | 匹配回车符。 |
\f | 匹配换页符。 |
\xnn | 匹配拉丁字符。比如 \xOA 等价于 \n。 |
\uxxxx | 匹配 Unicode 字符。比如 \u2028 匹配行终止符,\u2029 匹配段终止符。 |
\cX | 匹配 ctrl+X。比如 \cI 匹配 ctrl+I,等价于 \t。 |
[\b] | 匹配 Backspace 键(特殊记忆)。 |
. | 通配符,匹配除了少数字符(\n)之外的任意字符。 |
\d | 匹配数字,等价于 [0-9]。 |
\D | 匹配非数字, 等价于 [^0-9] |
\w | 匹配单词字符,等价于 [a-zA-Z0-9_]。 |
\W | 匹配非单词字符,等价于 [^a-zA-Z0-9_]。 |
\s | 匹配空白符,等价于 [ \t\v\n\r\f]。 |
\S | 匹配非空白符,等价于 [^ \t\v\n\r\f]。 |
量词
模式 | 说明 |
---|---|
{n,m} | 连续出现 n 到 m 次。贪婪模式。 |
{n,} | 至少连续出现 n 次。贪婪模式。 |
{n} | 连续出现 n 次。贪婪模式。 |
? | 等价于 {0,1}。贪婪模式。 |
+ | 等价于 {1,}。贪婪模式。 |
* | 等价于 {0,}。贪婪模式。 |
{n,m}? | 连续出现 n 到 m 次。惰性模式。最多匹配到 n 个 |
{n,}? | 至少连续出现 n 次。惰性模式。最多匹配到 n 个 |
{n}? | 连续出现 n 次。惰性模式。 |
?? | 等价于 {0,1}?。惰性模式。 |
+? | 等价于 {1,}?。惰性模式。 |
*? | 等价于 {0,}?。惰性模式。 |
贪婪模式――在匹配成功的前提下,尽可能多的去匹配
惰性模式――在匹配成功的前提下,尽可能少的去匹配
记录问题
1.设置全局标志,test() 的执行会改变正则表达式 lastIndex属性。连续的执行test()方法,后续的执行将会从 lastIndex 处开始匹配字符串,(exec() 同样改变正则本身的 lastIndex属性值)。
var regex = /en/g;
undefined
regex.test("hsh_en")
true
regex.test("hsh_en")
false
regex.test("hsh_en")
true
正则表达式带有g标识进行全局匹配时,匹配成功后,regex实例中会有一个lastIndex属性去记录本次命中正则的最后一位的下标+1,用于在下一次调用test的时候,从lastIndex开始进行匹配。
问题原因: 正则实例保存,反复使用,导致lastIndex值未重置。
解决办法: 每次进行正则校验时,都重新生成正则实例
var regex = new RegExp(/en/g);
regex.test("hhe_en")
// true
regex = new RegExp(/en/g);
/en/g
regex.test("hhe_en")
// true
// 或
/en/g.test("hhe_en")
// true
/en/g.test("hhe_en")
// true
2.回溯地狱导致页面卡死,详细参考
什么是回溯地狱?
正则在匹配的时候回溯过多,造成 CPU 100%,正常服务被阻塞。
使用场景记录:输入框校验,/^(\d+,?)+$/
validator(_, value) {
const newValue = value ? value.trim() : value;
if (newValue && !/^(\d+,?)+$/.test(newValue)) {
return Promise.reject(new Error("输入框内只支持数字和逗号,请检查!"));
}
return Promise.resolve();
}
js的正则引擎是nfa,所以就有回溯,可能导致匹配过慢。
正则表达式匹配目标字符串时,它从左到右逐个测试表达式的组成部分,看是否能找到匹配项。在遇到量词时,需要决定何时尝试匹配更多字符。在遇到分支时,必须从可选项中选择一个尝试匹配。每当正则做类似的决定时,如果有必要,都会记录其他选择,以便匹配不成功时进行回溯,到最后一个决策点,再重新进行匹配。——《高性能JavaScript》
回溯简单举例: /a(acd|bsc|bcd)/匹配“abcd”,
匹配步骤如下:
第一步:从正则表达式第一个字符a开始,吃进“abcd”的第一个字符,也是a,匹配成功!
第二步:这时正则表达式遇到了分支,后面有三种匹配可能,分别是acd、bsc、bcd。先选择第一个路径acd,吃入“abcd”的第二个字符,是b,匹配不成功。这时就需要进行一次回溯了(backtrack),把吃进来的最后一个字符b还回去,同时放弃第一个路径,选择第二个路径bsc;
第三步:第二个路径bsc中,第一个字符是b,吃进“abcd”中的第二个字符,也是b,匹配成功!
第四步:第二个路径bsc中,下一个字符是s,吃进“abcd”中的第三个字符是c,匹配失败,又要进行回溯了。把刚刚吃进的c和b还回去,回到第二步的状态,并选择第三个路径bcd;
第五步~第七步:依次匹配bcd和“abcd”中的剩余字符,均匹配成功。
第八步:完成匹配。
正则在线测试工具:regex101
只要不是逗号或者空(换行符、制表符等)都会导致回溯次数过多,因为这里多的字符正则表达式既匹配不到它又不能结束匹配,只能回溯,再遇到同样的问题,再回溯,直到页面奔溃。
优化:
- 尽可能的去让正则表达式准确化,越准确的正则表达式匹配时,回溯情况就越少,性能就越高。
- 可以做正则匹配的分级,尽可能使用一些性能比较高的正则表达式,先进行一些过滤匹配。在命中需要匹配的条件以后,再使用比较复杂的正则表达式进行匹配。从而避免复杂的正则表达式频繁的被调用。
const newValue = value ? value.trim().split(",").filter(i => i): [];
const length = newValue.length;
if (newValue && length > 0 && newValue.some(i => !/^\d+$/.test(i))) {
return Promise.reject(new Error("输入框内只支持数字和英文逗号,请检查!"));
} else if (value.match(/^.*?,,+.*$/)) {
return Promise.reject(new Error("输入格式不正确,请检查!"));
}
return Promise.resolve();