快速入门教程

语法

常用的元字符

量词元字符:设置出现的次数

* 0个或多个
+ 一个或多个
0个或一个
{n} 出现n次
{n, } 出现n到多次
{n, m} 出现n到m次

转义元字符:单个或组合在一起代表特殊含义

\ 转义字符 \b 单词边界
^ 以什么开头 $ 以什么结尾
. 除\n(换行符)以外的所有字符 \w 数字、字母、下划线中的任意字符
\d 表示数字 \D 表示非数字
\n 换行符 \r 换页符
\s 一个空白符(空格、换页符、制表符) \t 制表符(tab键,4个空格)
\f 换页符 () 表示分组
x|y x或y中的一个字符 [xyz] x或y或z中的一个字符
[^xyz] 除x/y/z之外的任意字符 [a-z] 指定a-z范围中的任意字符
(?:) 只匹配不捕获
(?=exp) 符合包含exp,匹配出左边的字符 (?<=exp) 符合包含exp,匹配出右侧字符
(?!exp) 符合不包含exp,匹配出左边字符 (?<!exp) 符合不包含exp,匹配出右侧字符

(?=) 正 先行断言:x(?=y) 表示匹配x仅仅当x后面跟着y。
(?!) 负 先行断言:x(?!y) 表示仅仅当x后面不跟着y时匹配x
(?<=) 正 后行预查 (?<=y)x 表示匹配x仅当x前面是y
(?<!) 负 后行预查 (?<!)x 表示匹配x仅当x前面不是y时匹配x
注:先行后行表示方向【先行匹配出左侧的字符,后行表示右侧字符】。正负表示存在还是否定

修饰符

i 表示不区分大小写
g 表示全局匹配
m 表示多行匹配
s 表示允许.匹配换行符
u 使用unicode码的模式进行匹配
y 执行“粘性(sticky)”搜索,匹配从目标字符串的当前位置开始。

惰性(.?)和贪婪性(.),?号的使用

.* 是单个字符匹配任意次,即贪婪匹配

  1. // 默认的贪婪模式匹配,会匹配到后边的}符合,即把中间的years old,Bob is内容给替换掉了
  2. var str = 'Anna is {age} years old,Bob is {age} years old too';
  3. var expr = /{.*}/g;
  4. console.log(str.replace(expr, '13'));
  5. /*输出: Anna is 13 years old too*/

.*? 是满足条件的情况只匹配一次,即懒惰匹配

  1. // 懒惰匹配,匹配到}就立马结束。把两个{age}都匹配是因为g修饰符
  2. var str = 'Anna is {age} years old,Bob is {age} years old too';
  3. var expr = /{.*?}/g;
  4. console.log(str.replace(expr, '13'));
  5. /*输出: Anna is 13 years old,Bob is 13 years old too*/

正则的方法exec和test

exec的返回值是数组,test的返回值是布尔值。

exec返回值含义

  1. let words= "happy learn RegExp's exec method";
  2. let regx=/(\w{3})Ex(.{3})/g; //第一个括号可以用\w进行匹配第二个必须用.才能匹配到',否则为null
  3. console.log(regx.exec(words));
  4. // 返回数组
  5. [
  6. "RegExp's",
  7. 'Reg',
  8. "p's",
  9. index: 12,
  10. input: "happy learn RegExp's exec method",
  11. groups: undefined
  12. ]

exec()接受一个字符串参数,返回包含第一个匹配项信息的数组;如果没有匹配项的情况下返回null
返回的数组是Array实例,并且包含额外的属性: index 和 input, groups。
其中,

  • index 表示匹配项在字符串中的位置。(匹配项为RegExp’s ,对应的位置是16);
  • input 表示应用正则表达式的字符串。(happy learn RegExp’s exec method);

在数组中,

  • 第一项:表示与整个模式匹配的字符串 (代码中的 RegExp’s 匹配 正则校验);
  • 其它项: 与表达式中用()包裹起来的,即捕获组匹配的字符串,第二项表示第一个捕获组的值,第三项表示第二个括号捕获的值(如果模式中没有捕获组regx = /\w{3}Ex.{3}/g;,则该数组只包含一项即RegExp’s)

    1. let words = "happy learn RegExp's exec method";
    2. let regx = /\w{3}Ex.{3}/g; // 没有捕获组
    3. console.log(regx.exec(words));
    4. [
    5. "RegExp's",
    6. index: 12,
    7. input: "happy learn RegExp's exec method",
    8. groups: undefined
    9. ]

    test返回布尔值

    1. let words = "happy learn RegExp's test method";
    2. let regx = /\w{3}Ex.{3}/g;
    3. let regOk = /\w{3}Ok.{3}/g;
    4. console.log(regx.test(words)); // true
    5. console.log(regOk.test(words)); // false

    字符串的正则方法有:match、replace

    match的返回值

    match的使用和exec的作用一样,只不过是string身上的方法。

    1. let words = "happy learn string's match method";
    2. console.log(words.match(/(\w{3})ing(.{2})/));
    3. // 返回结果
    4. [
    5. "string's",
    6. 'str',
    7. "'s",
    8. index: 12,
    9. input: "happy learn string's match method",
    10. groups: undefined
    11. ]

    replace的返回值加回调

    匹配值并进行替换

    1. var re = /apples/gi;
    2. var str = "Apples are round, and apples are juicy.";
    3. var newstr = str.replace(re, "oranges");
    4. console.log(newstr);
    5. //输出内容 oranges are round, and oranges are juicy.

    打印出通过()捕获的内容

    1. let words = "happy learn string's replace method";
    2. words.replace(/(\w{3})ing(.{2})/g, function ($1, $2) {
    3. console.log($1, $2); // $1 $2分别为两个()捕获到的内容
    4. });

    将所有单词首字母大写

    1. let words = "hello everybody, come on!";
    2. console.log(
    3. words.replace(/\b\w*\b/g, (word) => {
    4. return word.substring(0, 1).toUpperCase() + word.substring(1);
    5. })
    6. );
    7. // Hello Everybody, Come On!

    将句首单词首字母大写

    1. let words = "everybody, go! ";
    2. console.log(words.replace(/^\b(\w)/g, (L) => L.toUpperCase()));

    处理数字千分位

    https://www.yuque.com/beiniaonanyou/igewkv/gmn3o8#auIN0

    去除所有空格

    1. let words = "every body, go!";
    2. console.log(words.replace(/\s+/g, ""));
    3. // everybody,go!

    去除左侧空格

    1. let words = " everybody, go! ";
    2. console.log(words.replace(/^\s*/g, ""));
    3. // everybody, go!

    去除右侧空格

    1. let words = " everybody, go! ";
    2. console.log(words.replace(/\s*$/g, ""));
    3. // everybody, go!

    去除两端空格

    1. let words = " every body, go! ";
    2. console.log(words.replace(/(^\s*)|(\s*$)/g, ""));
    3. // every body, go!

    正则表达式的先行断言(lookahead)和后行断言(lookbehind)

    正则表达式的先行断言和后行断言一共有 4 种形式:

  • (?=pattern)零宽正向先行断言(zero-width positive lookahead assertion)

  • (?!pattern)零宽负向先行断言(zero-width negative lookahead assertion)
  • (?<=pattern)零宽正向后行断言(zero-width positive lookbehind assertion)
  • (?<!pattern)零宽负向后行断言(zero-width negative lookbehind assertion)

这里面的pattern是一个正则表达式。
如同^代表开头,$代表结尾,\b代表单词边界一样,先行断言和后行断言也有类似的作用,它们只匹配某些位置,在匹配过程中,不占用字符,所以被称为“零宽”。所谓位置,是指字符串中(每行)第一个字符的左边、最后一个字符的右边以及相邻字符的中间(假设文字方向是头左尾右)。
下面分别举例来说明这 4 种断言的含义。

(?=p) 正向先行断言

代表字符串中的一个位置,紧接该位置之后的字符序列能够匹配 p。
(?=p)表示p前面的位置,p只是一个参数值。
例如对“a regular expression”,要想匹配 regular 中的 re,但不能匹配 expression 中的 re,可以用re(?=gular),该表达式限定 re 右边的位置,这个位置之后是 gular,但并不消耗 gular 这些字符。
正则表达式 - 图1
将表达式改为re(?=gular).,将会匹配 reg,元字符.匹配了g,括号这一砣匹配了eg之间的位置。
正则表达式 - 图2

(?!p) 负向先行断言

代表字符串中的一个位置,紧接该位置之后的字符序列不能匹配 p。
表示不是p字母前面的位置,
例如对“regex represents regular expression”,要想匹配除 regex 和 regular 之外的 re,可以用re(?!g),该表达式限定了re右边的位置,这个位置后面不是字符g
正则表达式 - 图3

正向和负向的区别,在于该位置之后的字符能否匹配括号中的表达式。

先行和后行的区别,在于看匹配参数的左侧还是右侧。先行—-左侧,后行—-右侧

(?<=p) 正向后行断言

代表字符串中的一个位置,紧接该位置之前的字符序列能够匹配 p。
p字母右侧的位置
例如对regex represents regular expression,有 4 个单词,要想匹配单词内部的 re,但不匹配单词开头的 re,可以用(?<=\w)re,单词内部的 re,在 re 前面应该是一个单词字符。
之所以叫后行断言,是因为正则表达式引擎在匹配字符串和表达式时,是从前向后逐个扫描字符串中的字符,并判断是否与表达式符合,当在表达式中遇到该断言时,正则表达式引擎需要往字符串前端检测已扫描过的字符,相对于扫描方向是向后的。
正则表达式 - 图4

(?<!p) 负向后行断言

代表字符串中的一个位置,紧接该位置之前的字符序列不能匹配 p。
表示(不是p字母)右侧的位置。
例如对“regex represents regular expression”这个字符串,要想匹配单词开头的 re,可以用(?<!\w)re。单词开头的re,在本例中,也就是指不在单词内部的re,即re前面不是单词字符。当然也可以用\bre来匹配。
正则表达式 - 图5
对于这 4 个断言的理解,可以从两个方面入手:

  • 1、关于先行(lookahead)和后行(lookbehind):正则表达式引擎在执行字符串和表达式匹配时,会从头到尾(从前到后)连续扫描字符串中的字符,设想有一个扫描指针指向字符边界处并随匹配过程移动。先行断言,是当扫描指针位于某处时,引擎会尝试匹配指针还未扫过的字符,先于指针到达该字符,故称为先行。后行断言,引擎会尝试匹配指针已扫过的字符,后于指针到达该字符,故称为后行。
  • 2、关于正向(positive)和负向(negative):正向就表示匹配括号中的表达式,负向表示不匹配。

对这 4 个断言形式的记忆:

  • 1、先行和后行:后行断言(?<=pattern)、(?<!pattern)中,有个小于号,同时也是箭头,对于自左至右的文本方向,这个箭头是指向后的,这也比较符合我们的习惯。把小于号去掉,就是先行断言。
  • 2、正向和负向:不等于(!=)、逻辑非(!)都是用!号来表示,所以有!号的形式表示不匹配、负向;将!号换成=号,就表示匹配、正向。

我们经常用正则表达式来检测一个字符串中包含某个子串,要表示一个字符串中不包含某个字符或某些字符也很容易,用[^…]形式就可以了。要表示一个字符串中不包含某个子串(由字符序列构成)呢?
[^…]这种形式就不行了,这时就要用到(负向)先行断言或后行断言、或同时使用。
例如判断一句话中包含this,但不包含that
包含this比较好办,一句话中不包含that,可以认为这句话中每个字符的前面都不是that或每个字符的后面都不是that。正则表达式如下:
^((?<!that).)this((?<!that).)$

^(.(?!that))this(.(?!that))$
对于this is runoob test这句话,两个表达式都能够匹配成功,而this and that is runoob test都匹配失败。
正则表达式 - 图6
正则表达式 - 图7
在一般情况下,这两个表达式基本上都能够满足要求了。考虑极端情况,如一句话以that开头、以that结尾、thatthis连在一起时,上述表达式就可能不胜任了。 如runoob thatthis is the case或者this is the case, not that等。
只要灵活运用这几个断言,就很容易解决:
^(.(?<!that))this(.(?<!that))$
^(.(?<!that))this((?!that).)$
^((?!that).)this(.(?<!that))$
^((?!that).)this((?!that).)$
这 4 个正则表达式测试上述的几句话,结果都能够满足要求。
上述 4 种断言,括号里的 pattern 本身是一个正则表达式。但对 2 种后行断言有所限制,在 Perl 和 Python 中,这个表达式必须是定长(fixed length)的,即不能使用*、+、?等元字符,如(?<=abc)没有问题,但(?<=a*bc)是不被支持的,特别是当表达式中含有|连接的分支时,各个分支的长度必须相同。之所以不支持变长表达式,是因为当引擎检查后行断言时,无法确定要回溯多少步。Java 支持?、{m}、{n,m}等符号,但同样不支持*、+字符。Javascript 干脆不支持后行断言,不过一般来说,这不是太大的问题。
先行断言和后行断言某种程度上就好比使用if语句对匹配的字符前后做判断验证。

以下列出 ?=、?<=、?!、?<!= 的使用

exp1(?=exp2):查找 exp2 前面的 exp1。
正则表达式 - 图8
(?<=exp2)exp1:查找 exp2 后面的 exp1。
正则表达式 - 图9
exp1(?!exp2):查找后面不是 exp2 的 exp1。
正则表达式 - 图10
(?<!=exp2)exp1:查找前面不是 exp2 的 exp1。
正则表达式 - 图11

案例

数字千分位分割表示法

正则表达式 - 图12

  1. '12323232456'.replace(/(\d{1,3})(?=(\d{3})+$)/g,item=> item + ',')

分析:

  1. 首先匹配出一个逗号,使用(?=\d{3}$)就可以做到:

    1. var result = "12345678".replace(/(?=\d{3}$)/g, ',')
    2. console.log(result);
    3. // => "12345,678"
  2. 匹配出所有逗号,逗号出现的位置,要求后面3个数字一组,也就是\d{3}至少出现一次。可以使用量词+

    1. var result = "12345678".replace(/(?=(\d{3})+$)/g, ',')
    2. console.log(result);
    3. // => "12,345,678"
  3. 此时的正则不能适应所有情况,当长度是3的整数倍,就会在最前面加一个“,”,出现此现象是因为该正则从右往左一旦是3的倍数就在前面位置替换为“,”。

    1. var result = "123456789".replace(/(?=(\d{3})+$)/g, ',')
    2. console.log(result);
    3. // => ",123,456,789"
  4. 解决办法:要求匹配到的位置不能是开头。匹配开头可以使用^,但要求这个位置不是开头怎么办?可以使用(?!^) ```javascript var string1 = “12345678”, string2 = “123456789”; reg = /(?!^)(?=(\d{3})+$)/g;

var result = string1.replace(reg, ‘,’) console.log(result); // => “12,345,678”

result = string2.replace(reg, ‘,’); console.log(result); // => “123,456,789”

  1. 5. 匹配其他特殊情境。把"12345678 123456789"替换成"12,345,678 123,456,789"
  2. 修改正则,把里面的开头^和结尾$,替换成\b
  3. ```javascript
  4. var string = "12345678 123456789",
  5. reg = /(?!\b)(?=(\d{3})+\b)/g;
  6. var result = string.replace(reg, ',')
  7. console.log(result);
  8. // => "12,345,678 123,456,789"

(?!\b)怎么理解呢?要求当前是一个位置,但不是\b前面的位置,\b表示单词的边界。以边界结尾,但是不能以边界开头。

匹配两个字符串A与B中间的字符串包含A与B:

表达式: A.*?B(“.“表示任意字符,“?”表示匹配0个或多个)
示例: Abaidu.comB
结果: Awww.apizl.comB

匹配两个字符串A与B中间的字符串包含A但是不包含B:

表达式: A.*?(?=B)
示例: Awww.apizl.comB
结果: Awww.apizl.com

匹配两个字符串A与B中间的字符串且不包含A与B:

表达式: (?<=A).*?(?=B)
如果不包含前面匹配的字符写法(?<=要匹配的开始字符),不包含后面要匹配的字符写法(?=要匹配的结束字符)
示例: Awww.baidu.comB 结果: www.baidu.com