正则总结

正则表达式脱离语言,和数据结构与算法一样,作为程序员的软技能。目前存在的问题:不受重视 优点:

  • 显著的提升代码质量
  • 灵活多变
  • 强大的数据校验支持
1
2
3
4
var regex1 = /cosyer/g;
var regex2 = new RegExp('cosyer', 'g');
var regex3 = new RegExp(/cosyer/, 'g');
var regex4 = new RegExp(/cosyer/g);

其中的模式(pattern)部分可以是任何简单或复杂的正则表达式,可以包含字符类、限定符、分组、 向前查找以及反向引用。每个正则表达式都可带有一或多个标志(flags),用以标明正则表达式的行为。 正则表达式的匹配模式支持下列 3 个标志。

  • g:表示全局(global)模式,即模式将被应用于所有字符串,而非在发现第一个匹配项时立即停止;
  • i:表示不区分大小写(case-insensitive)模式,即在确定匹配项时忽略模式与字符串的大小写;
  • m:表示多行(multiline)模式,即在到达一行文本末尾时还会继续查找下一行中是否存在与模式匹配的项。

新增的修饰符:

  • u: Unicode模式。用来处理Unicode大于 \uFFFF的单个字符(超过\uFFFF会被程序员解析为两个字符)
  • y: 粘连模式。和g一样作为全局匹配,区别是g的下一次匹配不要求位置,但是y下一次匹配要求紧跟这这次匹配项之后
  • s: dotAll模式。正则表达式中点(.)作为匹配(除换行符\n,回车符\r,行分隔符,段分隔符)任意单个字符,支持点(.)真正匹配所有单个字符

两种使用方式

1
2
var a1 = /\+d/g; // 字面形式
var a2 = new RegExp('\\+d','g') // 构造函数

在JavaScript中建议使用字面形式的正则表达式,因为不需要担心字符串中的转义字符。比如上面示例代码中字面形式使用\d而构造函数使用的是\d;

1
2
3
var text = "aaa "; 
var pattern1 = /\s$/; //匹配字符串末尾的空格
pattern1.exec(text);

正则定义了很多特殊意义的字符,有名词,量词,谓词等

简单字符

没有特殊意义的字符都是简单字符,简单字符就代表自身,绝大部分字符都是简单字符,举个例子。

1
2
3
/abc/ // 匹配 abc
/123/ // 匹配 123
/-_-/ // 匹配 -_-

转义字符

\是转义字符,其后面的字符会代表不同的意思,转义字符主要有三个作用:

  1. 是为了匹配不方便显示的特殊字符,比如换行,tab符号等

  2. 正则中预先定义了一些代表特殊意义的字符,比如\w等

  3. 在正则中某些字符有特殊含义(比如下面说到的),转义字符可以让其显示自身的含义

常用转义字符 意义
\n 匹配换行符 (newline)
\r 匹配回车符 (return)
\t 匹配制表符,也就是tab键
\v 匹配垂直制表符
\x20 20是2位16进制数字,代表对应的字符
\u002B 002B是4位16进制数字,代表对应的字符
\u002B 002B是4位16进制数字,代表对应的字符
\w 匹配任何一个字母或者数字或者下划线 单子字符 (word)
\W 匹配任何一个字母或者数字或者下划线以外的字符 非单子字符
\s 匹配空白字符,如空格,tab等 (space)
\S 匹配非空白字符
\d 匹配数字字符,0~9 (digit)
\D 匹配非数字字符
\b 匹配单词的边界 (boundary)
\B 匹配非单词边界
\ 匹配\本身

字符集合

1
2
3
4
5
6
7
8
9
// 有时我们需要匹配一类字符,字符集可以实现这个功能,字符集的语法用[]分隔,下面的代码能够匹配a或b或c
[abc]

// 如果要表示字符很多,可以使用-表示一个范围内的字符,下面两个功能相同
[0123456789]
[0-9]

// 在前面添加^,可表示非的意思,下面的代码能够匹配abc之外的任意字符
[^abc]

其实正则还内置了一些字符集,在上面的转义字符有提到,下面给出内置字符集对应的自定义字符集

1
2
3
4
5
6
7
// 匹配除了换行符(\n)以外的任意一个字符 = [^\n]
\w = [0-9a-zA-Z_]
\W = [^0-9a-zA-Z_]
\s = [ \t\n\v]
\S = [^ \t\n\v]
\d = [0-9]
\D = [^0-9]

量词

如果需要匹配多次某个字符,正则也提供了量词的功能,正则中的量词有多个,如?、+、*、{n}、{m,n}、{m,}

{n}匹配n次,比如a{2},匹配aa

{m, n}匹配m-n次,优先匹配n次,比如a{1,3},可以匹配aaa、aa、a

{m,}匹配m-∞次,优先匹配∞次,比如a{1,},可以匹配aaaa…

?匹配0次或1次,优先匹配1次,相当于{0,1}

+匹配1-n次,优先匹配n次,相当于{1,}

*匹配0-n次,优先匹配n次,相当于{0,}

正则默认和人心一样是贪婪的,也就是常说的贪婪模式,凡是表示范围的量词,都优先匹配上限而不是下限

1
2
3
4
// a{1, 3} 匹配字符串'aaa'的话,会匹配aaa而不是a
var text = "aaa";
var pattern1 = /a{1,3}/;
pattern1.exec(text); //["aaa", index: 0, input: "aaa"] // index input为属性

有时候这不是我们想要的结果,可以在量词后面加上?,就可以开启非贪婪模式

1
2
3
var text = "aaa"; 
var pattern1 = /a{1,3}?/;
pattern1.exec(text); //["a", index: 0, input: "aaa"]

字符边界

^在[]外表示匹配开头的意思 ^abc // 可以匹配abc,但是不能匹配aabc

$表示匹配结尾的意思 abc$ // 可以匹配abc,但是不能匹配abcc 上面提到的\b表示单词的边界

\b表示匹配一个词的边界。一个词的边界就是一个词不被另外一个“字”字符跟随的位置或者没有其他“字”字符在其前面的位置。 abc\b // 可以匹配 abc ,但是不能匹配 abcc; /\bm/匹配“moon”中得‘m’; /oo\b/并不匹配”moon”中得’oo’,因为’oo’被一个“字”字符’n’紧跟着。 /oon\b/匹配”moon”中得’oon’,因为’oon’是这个字符串的结束部分。这样他没有被一个“字”字符紧跟着。 /\w\b\w/将不能匹配任何字符串,因为在一个单词中间的字符永远也不可能同时满足没有“字”字符跟随和有“字”字符跟随两种情况。

选择表达式

有时我们想匹配x或者y,如果x和y是单个字符,可以使用字符集,[abc]可以匹配a或b或c,如果x和y是多个字符,字符集就无能为力了,此时就要用到分组

正则中用|来表示分组,a|b表示匹配a或者b的意思

1
123|456|789 // 匹配 123 或 456 或 789

分组与引用

分组是正则中非常强大的一个功能,可以让上面提到的量词作用于一组字符,而非单个字符,分组的语法是圆括号包裹(xxx)

1
(abc){2} // 匹配abcabc

分组不能放在[]中,分组中还可以使用选择表达式

1
(123|456){2} // 匹配 123123、456456、123456、456123

和分组相关的概念还有一个捕获分组和非捕获分组,分组默认都是捕获的,在分组的(后面添加?:可以让分组变为非捕获分组,非捕获分组可以提高性能和简化逻辑

1
2
3
'123'.match(/(?:123)/) // 返回 ['123']
'123'.match(/(123)/) // 返回 ['123', '123']
// 和分组相关的另一个概念是引用,比如在匹配html标签时,通常希望<xxx></xxx>后面的xxx能够和前面保持一致

引用的语法是\数字,数字代表引用前面第几个捕获分组,注意非捕获分组不能被引用

1
<([a-z]+)><\/\1> // 可以匹配 `<span></span>` 或 `<div></div>`等

预搜索

如果你想匹配xxx前不能是yyy,或者xxx后不能是yyy,那就要用到预搜索

js只支持正向预搜索,也就是xxx后面必须是yyy,或者xxx后面不能是yyy

1
2
1(?=2) // 可以匹配12,不能匹配22
1(?!2) // 可有匹配22,不能匹配12

修饰符

默认正则是区分大小写,这可能并不是我们想要的,正则提供了修饰符的功能,修复的语法如下

1
/xxx/gi // 最后面的g和i就是两个修饰符

g正则遇到第一个匹配的字符就会结束,加上全局修复符,可以让其匹配到结束

i正则默认是区分大小写的,i可以忽略大小写

m正则默认情况下,^和$只能匹配字符串的开始和结尾,m修饰符可以让^和$匹配行首和行尾,不理解就看例子

1
2
3
4
5
/jing$/ // 能够匹配 'yanhaijing,不能匹配 'yanhaijing\n'
/jing$/m // 能够匹配 'yanhaijing, 能够匹配 'yanhaijing\n'

/^jing/ // 能够匹配 'jing',不能匹配 '\njing'
/^jing/m // 能够匹配 'jing',能够匹配 '\njing'

js使用正则表达式的方法

正则表达式是用于匹配字符串中字符组合的模式。在 JavaScript中,正则表达式也是对象。这些模式被用于 RegExp 的 exec 和 test 方法, 以及 String 的 match、replace、search 和 split 方法

方法 描述
exec 一个在字符串中执行查找匹配的RegExp方法,它返回一个数组(未匹配到则返回null)。
test 一个在字符串中测试是否匹配的RegExp方法,它返回true或false。
match 一个在字符串中执行查找匹配的String方法,它返回一个数组或者在未匹配到时返回null。
search 一个在字符串中测试匹配的String方法,它返回匹配到的位置索引,或者在失败时返回-1。
replace 一个在字符串中执行查找匹配的String方法,并且使用替换字符串替换掉匹配到的子字符串。
split 一个使用正则表达式或者一个固定字符串分隔一个字符串,并将分隔后的子字符串存储到数组中的String方法。

当你想要知道在一个字符串中的一个匹配是否被找到,你可以使用test或search方法;想得到更多的信息(但是比较慢)则可以使用exec或match方法。如果你使用exec或match方法并且匹配成功了,那么这些方法将返回一个数组并且更新相关的正则表达式对象的属性和预定义的正则表达式对象(详见下)。如果匹配失败,那么exec方法返回null(也就是false)。

1
2
3
4
var text = "cat, bat, sat, fat"; var pattern1 = /.at/;

var matches = pattern1.exec(text);
console.log(matches) //["cat", index: 0, input: "cat, bat, sat, fat"]

匹配规则

关键字匹配

常用的匹配方式,通常是通过匹配关键词来实现定位

示例1:正则设定手机号格式

1
2
var regex = /^(086-)?1[345789]\d{9}$/;
regex.test('15512341234'); // true

示例2:正则将时间格式转换:年/月/日(yyyy/mm/dd)

1
2
3
4
var date = new Date();
var time = date.toISOString();
var regex = /^(\d{4})-(\d{2})-(\d{2})(.+)$/;
time.replace(regex, '$1/$2/$3'); // "2019/08/23"

位置匹配

常用的位置匹配就是^(匹配字符串开头)和$(匹配字符串结尾)。 另外还有\b(匹配一个单词边界),\B(匹配非单词边界)

示例1:把字符串中所有单词的首字母大写

1
2
3
var regex = /\b[a-z]/g;
var str = 'i am cosyer who is a web developers';
str.replace(regex, word => word.toUpperCase()); // "I Am Cosyer Who Is A Web Developers"

示例2:把字符串中所有每超过3个字符的单词就增加一个短横(-)

1
2
3
var regex = /[a-zA-Z]{3}\B/g;
var str = 'i am cosyer who is a web developers';
str.replace(regex, word => word + '-'); // "i am cos-yer who is a web dev-elo-per-s"

高级用法

贪婪匹配

  • 贪婪匹配:趋向于最大长度匹配。(默认)
  • 非贪婪匹配:匹配到结果就好。

示例1:贪婪匹配获取数字

1
'123456789'.match(/\d+/)[0]; // "123456789"

示例2:非贪婪匹配获取数字

1
'123456789'.match(/\d+?/)[0]; // "1"

懒惰匹配

上文中的非贪婪匹配就用到了惰性匹配,其特点就是在其他重复量词后面加上限定符(?) 惰性匹配的特点:

  • 重复量词后面添加限定符(?)
  • 惰性匹配会尽可能少的匹配字符,同时必须满足整个匹配模式。

示例1:非惰性匹配和惰性匹配

1
2
'123412341234'.match(/1(\d+)4/)[0]; // "123412341234"
'123412341234'.match(/1(\d+?)4/)[0]; // "1234"

先行断言/先行否定断言

先行断言:x只有在y前面才匹配,必须写成/x(?=y)/的形式。 先行否定断言:x只有不在y前面才匹配,必须写成/x(?!y)/的形式。 匹配的结果是x,如通过match方法匹配项会返回x。

示例1:先行断言和先行否定断言的用法

1
2
3
4
5
/cosyer(?=2019)/.test('cosyer2019'); // true
/cosyer(?=2019)/.test('cosyer-2019'); // false

/cosyer(?!2019)/.test('cosyer2019'); // false
/cosyer(?!2019)/.test('cosyer-2019'); // true
我们可以发现,这里同时包含了关键词匹配和位置匹配,属于正则的高级匹配。 接下来我们可以看看怎么处理业务中的问题。

示例2:通过正则把数字 1234567890 转换成美元 $1,234,567,890

1
2
3
var str = '1234567890';
var regex = /(?!^)(?=(\d{3})+$)/g;
str.replace(regex, ',').replace(/^/, '$'); // "$1,234,567,890"

后行断言/后行否定断言

后行断言和先行断言正好相反。

后行断言:x只有在y后面才匹配,必须写成/(?<=y)x/的形式。 后行否定断言:x只有不再y后面才匹配,必须写成/(?<!y)x/的形式。 匹配的结果是x,如通过match方法匹配项会返回x。

示例1:后行断言和后行否定断言的用法

1
2
3
4
5
/(?<=2019)cosyer/.test('2019cosyer'); // true
/(?<=2019)cosyer/.test('2019-cosyer'); // false

/(?<!2019)cosyer/.test('2019cosyer'); // false
/(?<!2019)cosyer/.test('2019-cosyer'); // true
示例2:通过正则把美元 $123,456,789,000 变成美元 $1234,5678,9000 试着通过先行断言+先行否定断言来处理
1
2
3
var str = '$123,456,789,000';
var regex = /(?!^)(?=(\d{4})+$)/g;
str.replace(/,/g, '').replace(regex, ','); // "$,1234,5678,9000"
由于第一个字符是$而不是数字,在这里先行否定断言判断字符串初始无效,所以在$和数字中间出现了逗号分隔符。 同时也不能把(?!^)改成(?!$),因为匹配到1234,56789,000的时候前面的字符也不是$而是1,所以仍然会在开头添加一个逗号(,)。
1
2
3
var str = '$123,456,789,000';
var regex = /(?!\$)(?=(\d{4})+$)/g;
str.replace(/,/g, '').replace(regex, ','); // "$,1234,5678,9000"
我们发现,我们只需要排除一种情况,就是$后面跟着逗号的情况。 这个时候满足后行断言匹配项(x)在美元符号$后面。 试着通过后行否定断言代替先行否定断言。
1
2
3
var str = '$123,456,789,000';
var regex = /(?<!\$)(?=(\d{4})+$)/g;
str.replace(/,/g, '').replace(regex, ','); // "$1234,5678,9000"

本文结束感谢您的阅读

本文标题:正则总结

文章作者:陈宇(cosyer)

发布时间:2018年10月14日 - 22:10

最后更新:2020年05月08日 - 01:05

原始链接:http://mydearest.cn/%E6%AD%A3%E5%88%99%E6%80%BB%E7%BB%93.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

坚持原创技术分享,您的支持将鼓励我继续创作!