正则从入门到🛫

442 阅读13分钟

简介

正则表达式 是使用单个字符串来描述,匹配一系列符合某个规则的字符串,一般用来检索、替换匹配的文本

价值

  1. 编辑器中,可以快速查找指定的文本

  2. 编辑器中,将文本格式快速批量转换,比如把若干个手机号,生日,转换格式

  3. 配合 find,grep 来搜索文件

  4. 表单提交时进行校验,如手机号,邮箱,IP 地址等

  5. 配置文件,如 webpack 的 loader 也支持正则

常用工具

  1. 可视化常看正则表达式结构: Regulex

  1. 正则表达式在线测试:regex101

  2. 正则表达式在线测试中文版:regex101-cn

创建方式

  1. 字面量

由斜杠包围,而不是引号包围

// /pattern/flags
/ab/g
  1. 构造函数

由引号包围,不包含在斜杠之间

new RegExp('ab+c', 'i'); // 字符串形式
new RegExp(/ab+c/, 'i'); // es6 加强,构造函数也可以传递正则字面量
  1. 工厂方法

RegExp('ab+c', 'i')
RegExp(/ab+c/, 'i') // es6 加强

基本语法

特殊字符:包括字符组,重复匹配,分组,分支,位置匹配,反向引用,环视

正则是由字面字符和其定义的特殊字符组成。

字面字符指的是 abc, 123 等纯文本,特殊字符较为复杂

特殊字符:

字符组: []、-、^、 .、 \d、 \D、 \w、 \W、 \s 、\S

空白字符: \r、 \n、 \f、 \t、 \v 分别是 回车、换行、换页、水平制表符、垂直制表符

重复匹配:*、 +、?、{}

贪婪与懒惰:?

分组:()

非捕获组:(?:p) 如果是 ()内是一个分组的话,非捕获组的意思就是不会把正则匹配到的内容保存到分组里面

反向引用:\1 ~ \10

分支:()、 |

位置匹配:^、 $ 、\b、 \B、 (?=p)、 (?!p)

转义:\

1.1 绝对匹配

如果我们需要匹配固定的文本,也就是绝对匹配,那么直接使用字面字符即可

// 表达式
/hello/

// 结果
hello world

js 正则表达式支持以 Unicode 来引用特殊字符

1.2 字符组

使用正则,更多是为了模糊匹配,所谓的模糊匹配,就是要匹配的字符在某个字符组内,定义字符组使用特殊字符 []

需求:匹配 cat、cet、cft

// 表达式
/c[aef]/t

// 结果
// cat cbt cct cet cft

注意: 字符组 [] 所在的位置就是占用一个字符,该位置的字符可以是组富足的任意一个字符, 属于或逻辑

1.2.1 范围字符组

对于连续的字符,我们可以使用特殊字符 - 来表示一个范围组成的字符组。- 字符的两边可以是任意的字符。这是是根据 ASCII 来圈定范围。

而我们常用的字符组有: [0-9]、 [a-z]、 [A-Z]。 也可以一起使用 [0-9a-zA-Z]。 由于 A 到 z 之间有其它的字符,所以大小写需要分开写。

注意: - 字符必须位于两个字符中间,如果不是,那么就会变成普通的字符。

// 表达式
/#[0-9a-zA-Z]{6}/

// 结果
// #ffffff #123123 #09efj2
 
 // 表达式
 /c[-az]t/ 
 // 结果
 // cat cbt cct cdt cet cft c-t

ASCII

1.2.2 排除字符组

有时候,我们需要该位置的字符除了某些字符,可以是其它的任意字符,这种情况就可以使用到排除字符组: ^

^ 也可以匹配开始位置,同样的字符在不同的位置,会有不同的语法作用

如果取反字符在字符组的开头,表示对整个字符组进行取反,并不是仅仅取反后面一个字符

注意, ^ 字符必须在字符组的开头位置,如果不是,那么就丧失了特殊功能, 相当于普通的字符

// 表达式
/c[^aec]t/ 
// 结果
// cat cbt cct cet cft c^t

// 表达式
/c[a^ec]t/
// 结果
// cat cbt cct cdt cet cft c^t

1.2.3 预定义字符组

系统中预定义了一些常见的字符组,我们可以直接使用

\d: 任何一个数字字符,等价于 [0-9]

\D: 任何一个非数字字符,等价于[^0-9]

\w: 任何一个字母,数字或下划线字符,等价于[0-9a-zA-Z_]

\W: 任何一个非字母,数字或下划线字符,等价于[^0-9a-zA-Z_]

\s: 任何一个空白字符,等价于[\f\n\t\v\r]

\S: 任何一个非空白字符,等价于[^\f\n\r\v\t]

.: 任何一个字符,除了换行符,回车符,行分隔符和段分隔符

一个字符组只会匹配一个字符

1.3 重复匹配

不管绝对匹配或者字符组匹配,都只能匹配一个字符。

正则表达式提供重复匹配的能力,避免每个位置的匹配规则都需要重新写一遍

重复匹配的主要操作符是: {m,n} 。 也就是该操作符前面的字符可以重复 m 到 n 次

重复匹配两次的作用对象是前面的字符或者分组

// 表达式
/a\d{2,4}b/

// 结果
// a1b a12b a123b a12345b

系统提供了几个简写情况:

* :表示可以出现任意次,等价于 {0,}。

?:表示出现0次或一次,等价于 {0,1}。

+:表示至少出现一次,等价于 {1,}。

{m}:表示出现 m 次,这是固定次数。

{n,}:表示至少出现 n 次。

1.3.1 贪婪模式

这里需要注意的是,重复匹配默认是贪婪模式,也就是在匹配的时候,会匹配所有可能的字符

const regex = /\d{2,5}/g
const string = "123 1234 12345 123456"
console.log(string.match(regex))

// ["123", "1234", "12345", "12345"]

不过有时候,我们希望它只匹配最小满足的字符就可以了,就需要手动设置为非贪婪模式,在量词后面加上 ? 即可。 非贪婪就是有匹配结果就结束了

const regex = /\d{2,5}?/
const string = "123 1234 12345 123456"
console.log(string.match(regex))

// ['12', '12', '34', '12', '34', '12', '34', '56']

再举个例子,比如我们希望匹配获取 div 标签的 id 属性值

<div id="container" class="classname">123</div>

默认贪婪模式

/id=".*"/
<div id="container" class="classname">123</div>

非贪婪模式

/id=".*?"/
<div id="container" class="classname">123</div>

1.4 分组

只要给子表达式加上括号 (), 那么该子表达式匹配的内容就会独立保存在一个组内。 给一个子表达式分组,就可以认为匹配的结果为单一的实体来使用

/(\d{4})-(\d{2})-\d{2}/

分组之后,我们就可以获取到组的内容。子表达式加上括号以后,就成了一个捕获组,我们就能使用和获取

使用主要有两种,分别是 捕获 和 反向引用

1.4.1 捕获

捕获主要在匹配结果中使用,配合相关 Api 进行,比如 String 的 match 方法或者 Regexp 的 exec 方法

捕获会导致性能的损耗

const regex = /(\d{4})-(\d{2})-\d{2}/
const string = "2021-03-12";
string.match(regex)

另外调用正则表达式对象的 test 和 exec 方法后,RegExp 全局构造函数的静态属性 11-9 也保存了上次匹配结果的分组信息。

注意:在反向引用中,如果编号大于9就会出现二义性,比如 \10 是表示第十个捕获组还是表示第一个捕获组和一个字符0。在javascript中,如果存在第10个捕获组,则引用对应的分组;如果不存在,则引用\1

1.4.2 命名分组

ES2017 的新特性

语法规则:

  • 分组:(?)
  • 提取:$
  • 反向引用:\k
var regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
var text = '2018-12-30';
var result = text.replace(regex, '$<month>/$<day>/$<year>');
console.log(result);   // 12/30/2018
//反向引用
var regex = /\d{4}(?<split>-|/|.)\d{2}\k<split>\d{2}/;
var text = '2018-12-30';
console.log(regex.test(text));  // true

1.4.3 非捕获组

(?:pattern) 匹配 pattern 但是不获取匹配结果,也不会进行存储

const reg = /(?:re).*(er)-\1/g
const str = 're-retester-er'
reg.exec(str);// ['re-retester-er', 'er'] 匹配成功

const reg4 = /(re).*(er)-\1/g
const str4 = 're-retester-re'
reg4.exec(str4);// ["re-retester-re","re", "er"]

1.5 分支

当我们匹配的目标文本或者字符串存在多种可能,一个正则表达式无法满足的时候,那么就可以使用正则表达式提供的或 ( | ) 操作符,将其分解为多条路径,也就是分支

1.5.1 分支的分割是基于组

// 表达式
const regex = /There is a cat|dog|pig/g

// 结果
There is a cat
There is a dog
There is a pig

分支操作符 | 会将左右两边分为两个独立的正则表达式

而分割的范围是基于分组的,在上面的例子里, 因为没有显式的使用括号进行分组,其实整个正则表达式就是一个分组,所以上面会被分解成三个表达式

如果我们想在某个子表达式中使用分支,那么需要先把子表达式进行分组

// 表达式
There is a (cat|dog|pig)

1.5.2 分支是懒惰模式

// 表达式
const regex = /java|javascript/g

// 结果
java
javascript

可以发现,对于字符串 JavaScript 实际上是没有匹配的,只匹配了前面的 java 子串

这个是因为分支是懒惰的,匹配的时候,先试用第一个分支表达式进行匹配,不行才会尝试其它分支。当所有分支都失败的时候,才会匹配失败

所以使用分支,我们需要注意各个分支的顺序

1.6 位置匹配

位置匹配用于指定应该在什么地方进行匹配操作,位置可以简单理解为字符之间的空隙

JS 正则表达式目前有 6 个操作符可以用来匹配位置, 分别是 ^、 $、 \b、 \B、 (?=p)、 (?!p)

^: 匹配开头,在多行匹配中匹配开头

$:匹配结尾,在多行匹配中匹配

\b:单词边界,也就是\w 和 \W 之间的位置,也包括了 \W 和 ^、$ 之间的位置,

\B:非单词边界

(?=p):肯定式的向前,匹配子表达式 p 匹配到的子字符串前面位置

(?!p):否定式的向前,匹配不是子表达式 p 匹配到的子字符串前面的位置

位置匹配有两个作用

  1. 插入

  2. 匹配过滤

1.6.1 位置插入

^ 和 $
const result = "hello\nhello".replace(/^|$/g, "#");
// #hello\nword"

const result = "hello\nhello".replace(/^|$/mg, "#");
// "#hello#\n#hello#"

^: 匹配开头,在多行匹配模式下(m)中会匹配每一行的开头

$:匹配结尾,在多行匹配模式下(m)中会匹配每一行的结尾

\b 和 \B
const helloWorld = "hello world";
const res = helloWorld.replace(/\b/g, "#")
//  '#hello# #world#' 

const res2 = helloWorld.replace(/\B/g,"#")
// 'h#e#l#l#o w#o#r#l#d'

\b 匹配单词和非单词字符之间的位置。 \B 相反,匹配单词和单词字符之间的位置

(?=p) 和(?!p) 【非所有浏览器支持】
const result = "hello".replace(/(?=ll)/g, '#');
// he#llo

const result = "hello".replace(/(?!ll)/g, '#');
// #h#el#l#o#

(?=p) 可以理解为子表达式 p 的匹配,只不过匹配结果不是返回表达式匹配的结果,而是返回该结果前面的位置。

(?!p) 同理,先看子表达式 p 的匹配结果,返回所有非结果的子字符串的前面位置。

(?=p) 和 (?!p) 有些地方翻译为环视(能够前后查看),其实,这个理解为匹配表达式 p 前面的位置就可以了。

1.7 转义

在正则表达式的特殊字符发挥其特殊作用语法的位置, 如果需要使用该字符本身,那么就需要进行转义, 比如 使用 \ , 那么就需要使用 \

无需转义
/a[a^c]t/ 
为了匹配 cat需要转义
/a[^ac]t/

1.8 flags

g全局匹配:找到所有匹配,而不是找到一个后就停止
i忽略大小写
m多行匹配
y粘性匹配,仅匹配 lastIndex之后的字符串
s点号匹配所有字符,允许 . 去匹配结束符
u使用 unicode 码的模式去匹配

使用正则表达式相关 Api

在 JS 里,有多个和正则相关的 Api,分别是

  1. RegExp.prototype.exec
  2. RegExp.prototype.test
  3. String.prototype.search
  4. String.prototype.match
  5. String.prototype.matchAll
  6. String.prototype.split
  7. String,.prototype.replace

2.1 RegExp.prototype.exec

regexObj.exec(str)

在一个指定的字符串里执行一次搜索匹配,(不管是否开启全局模式,即 g,只要匹配到一个就停止,返回一个结果数组或者 null)

返回结果包括匹配的子串和捕获组

同时,返回的数组还具有以下属性

  • groups: 命名捕获组集合或者 undefined(如果没有定义命名捕获)(参考 es6 的具名匹配)
  • index:匹配结果的开始位置
  • input:搜索的字符串

demo:

const regex = /(?<year>\d{4})-(?<month>\d{2}-\d2)/
const string = "2021-03-12 2022-10-11"
const res = regex.exec(string)

需要注意的是,当正则表达式开启全局模式之后,每次调用 exec 都只会匹配一次,并且更新 lastIndex,也就是下一次开始匹配的位置。如果不开启全局模式,那么 lastIndex 就永远是 0,也就是每次都要从文本开始的地方进行匹配

const regex = /(\d{4})-(\d{2})-\d{2}/g
const string = "2021-03-12 2012-01-15"
// 第一次执行
regex.exec(string)
(3) ["2021-03-12", "2021", "03", index: 0, input: "2021-03-12 2012-01-15", groups: undefined]
regex.lastIndex // 10

// 第二次执行
regex.exec(string)
(3) ["2012-01-15", "2012", "01", index: 11, input: "2021-03-12 2012-01-15", groups: undefined]
regex.lastIndex // 21

如果我们想要匹配所有的结果,我们需要写个循环,从 lastIndex 的位置开始匹配,如果没有匹配成功则返回 null,丛植 lastIndex=0;

const regex = /(\d{4})-(\d{2})-\d{2}/g
const string = "2021-03-12 2012-01-15"
let result;
while (result = regex.exec(string)) {
  console.log( result, regex.lastIndex ); 
}

显然,使用 ecex 来获取正则表达式全局模式下所有的匹配结果是有点繁琐的

所以,如果我们只是简单地想获取所有的匹配结果, 使用 string 的 match 方法会更加的方便。

但是 exec 可以获取匹配结果在源字符串中的位置

2.2 String.prototype.match

str.match(regex)

当正则表达式没有开启凯泉模式的时候,每次调用都会执行一次匹配, 有匹配结果就返回,否则就返回 null。返回的结果数组和 ecex 方法一致

2.1 提到,为了获取全局模式下所有的匹配结果,如果使用 exec 就需要使用循环,而 match 就弥补了这个不足,但是也缺少了一些信息

"2022-01-01 2022-02-02".match(/\d{4}-\d{2}-\d{2}/)

"2022-01-01 2022-02-02".match(/\d{4}-\d{2}-\d{2}/g)

如果需要 group,index 等信息,那么就不能使用全局模式

在非全局模式下, 使用 exec 和 match 都可以

开启全局模式后,如果为了得到匹配结果,那么使用 atch 更加高效。

此外,string 还有一个 matchAll 方法

[..."2022-01-01 2022-02-02".matchAll(/\d{4}-\d{2}-\d{2}/g)]

2.3 String.prototype.replace

str.replace(regexp|substr, newSubStr|function)

第二个参数为函数

当正则表达式使用全局模式的时候,每匹配到一个结果都会执行一次第二个参数的函数,函数返回值为匹配结果子串的替换值

replacer(match,(p1,p2,p3...), offset, string, nameCaptureGroup) = > string

Match : 本次匹配结果子串

p1,p2:捕获组子串

offset:匹配结果在原字符串的偏移值

string:被匹配的原字符串

namedCaptureGroup:命名捕获组匹配的对象

"2021-03-12 2012-01-15".replace(/(?<year>\d{4})-(\d{2})-\d{2}/g, (...arr) => console.log(arr) ) /*
0:"2021-03-12"
1:"2021"
2:"03"
3:0
4:"2021-03-12 2012-01-15"
5:{year:"2021"}
length:6
*/
/*
"2012-01-15"
1:"2012"
2:"01"
3:11
4:"2021-03-12 2012-01-15"
5:{year:"2012"}
length:6
*/

第二个参数值为字符串

    当第二个字符为字符串的时候,我们可以通过一些特殊字符来引用到匹配的相关信息

    $1, $2 $99 获取第 1-99 个捕获组的文本

    $&: PIP诶到的子串文本

    $` : 匹配到的子串的左边文本

    $' :匹配到的子串的右边文本

    $$: 美元符号
"start2021-03-12end".replace(/(\d{4})-(\d{2})-(\d{2})/g, "([$&][$1][$2][$3][$`][$'][$$1]");

// 'start([2021-03-12][2021][03][12][start][end][$1]end'
start 是原字符串的开头
([ 是 替换字符串的左边符号
2021-03-12 是  $&
][ 是替换字符串的字符
然后一次是$1 $2 $3
再次的 start 是 $`
后面的 end 是$'
$$1 前面两个是美元符号,所以最终是 $1
最后的 end 是原字符串的结尾

demo:将每个单词的首字母转换为大写

'my name is jontyy' =>  'My Name Is Jontyy'
// 方法一
function titleize (str) {
  // ?: 意思是非捕获组,不会被算在匹配的结果里
  // 所以必须是以空格开头,或者当前字符就是首字符的 字符,然后大写
  return str.toLowerCase().replace(/(?:^|\s)\w/g, (c) => {
    return c.toUpperCase();
  });
}
console.log( titleize('my name is epeli') );


// 方法二
function titleize (str) {
  // 边界的 word 大写
  return str.toLowerCase().replace(/\b\w/g, (c) => {
    return c.toUpperCase();
  });
}
console.log( titleize('my name is epeli') );

2.4 String.prototype.replaceAll()

str.replaceAll(regexp|substr, newSubStr|function)

replaceAll方法会将所有匹配pattern的子字符串用提供的方法或者字符串进行替换,并返回一个新的字符串,原始字符串不会被改变

当使用正则表达式作为参数时,必须要将正则表达式设置全局标志,否则会抛出TypeError异常;当使用字符串作为第一个参数时,仅仅是作为字符串,不会将其转化为正则表达式

'aabbcc'.replaceAll('b', '.');
// 'aa..cc'
'aabbcc'.replaceAll(/b/, '.');
// TypeError: String.prototype.replaceAll called with a non-global RegExp argument
'aabbcc'.replaceAll(/b/g, '.');
// "aa..cc"

2.5 String.prototype.search

使用:

str.search(regexp)

search 就是在字符串中搜索匹配到的正则

如果匹配成功,那么就返回正则表达式匹配结果在字符串中首次出现的索引,没有匹配到就返回-1

不管正则表达式是否为全局模式,都只会返回第一个匹配的子串的开始索引

"a ab aba abab ababab".search(/ab/g)
// 2

如果要查找绝对匹配的子串,使用 String.prototype.indexOf 会更加高效和方便,当需要查找不确定的,需要通过正则表达式去匹配的子串,那 search 会更加方便

Search 方法每次都是从字符串开头向后找的,不会记录上一次结果,所以需要配合裁剪等操作

2.6 RegExp.prototype.test

regexObj.test(str)

测试该正则表达式是否可以在字符串 str 中匹配到结果,只要有一个结果那么就返回 true,否则就返回 fasle

/(\d{4})-(\d{2})-\d{2}/.test("2021-03-12 2012-01-15") // true

与 search 功能一样,但是返回值不同,分别是数字和布尔

const str = 'table football';
const reg = RegExp('foo*');
const gReg = RegExp('foo*', 'g');
console.log(reg.test(str), reg.lastIndex); // true 0
console.log(gReg.test(str), gReg.lastIndex); // true 9
console.log(gReg.test(str), gReg.lastIndex); // false 0 匹配失败,lastIndex重置为0

2.7 String.prototype.split

Split 比较常用,用来分割字符串,比如

'java,javascript'.split(',') // ["java", "javaschript"]

有两个点比较有趣

  1. 第二个参数可以指定返回结果长度
  2. 使用正则表达式,返回结果会包含分隔符
'java,javascript,php'.split(',',2)
// ["java","javascript"]

'java,javascript,php'].split('/(,)/')
// ["java", ",", "javascript",",", "php"]

2.8 RegExp.lastIndex

lastIndex 是正则表达式一个可读可写的实例上整形属性,用来指定下一次匹配的起始索引

只有正则表达式用了全局检索的 g 和粘性检索的 y 标志之后,这个属性才会有作用

  • 如果 lastIndex 大于字符串的长度,那么test 和 exec 方法会匹配失败,然后 lastIndex 被重置为 0

  • 如果 lastIndex 等于或者小于字符串长度,那么正则表达式从 lastIndex 开始匹配

    • 如果 test 和 exec 匹配成功,那么 lastIndex 会被设置为紧随最近一次成功匹配的下一个位置
    • 如果 test 和 exec 匹配失败,lastIndex 会被设置为 0

2.9 用途

验证: RegExp.prototype.test RegExt.prototype.search

切分: String.prototype.split

提取:RegExp.prototype.exec RegExp.prototype.match

替换:String.prototype.replace

优化建议

  1. 对于重复使用的正则表达式,需要保存在变量中,避免每次创建都实时编译

  2. 避免回溯,尽量避免使用重复量词

  3. 不需要引用分组信息的时候,使用非捕获组

  4. 关注如何让匹配快速失败:正则表达式慢的主要原因是匹配失败过程慢

  5. 以简单的,必须的字符开始,应该尽可能快速测试明显不匹配的位置

  6. 减少分支数量,缩小分支范围。可以使用选项组或者将分组位置推后

字符

基本元字符:

.默认匹配除换行符之外的任何单个字符在字符集内,点失去了它的特殊意义,并与文字点匹配ES2018 添加了 s“dotAll”标志,它允许点也匹配行终止符。
逻辑或操作符
[]匹配字符集合中的一个字符
[^]对字符集求非
-定义一个区间如:[A-Z]
\对下一个字符进行转义如:(

数量元字符:

*匹配前一个字符(子表达式)零次或多次
*?*的懒惰版本
+匹配前一个字符(子表达式)一次或多次
+?+的懒惰版本
?匹配前一个字符(子表达式)零次或一次
{n}匹配前一个字符(子表达式)n次
{m, n}匹配前一个字符(子表达式)m-n次
{n, }匹配前一个字符(子表达式)至少n次
{n, }?{n, }的懒惰版本

位置元字符

^匹配一行的开始, 多行模式下能识别\n
$匹配一行的结尾, 多行模式下能识别\n
\b匹配单词的边界(开头和结束)
\B\b的反义

特殊字符元字符

\c匹配一个控制字符
\d匹配任意数字字符 等价 [0-9]
\D\d的反义 [^0-9]
\f匹配换页符。
\n匹配换行符。
\r匹配回车符。
\s匹配单个空白字符,包括空格、制表符、换页符、换行符和其他 Unicode 空格。相当于 [ \f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]
\S\s的反义
\t匹配水平制表符。
\v匹配垂直制表符。
\w匹配任意字母数字字符or下划线字符 等价 [A-Za-z0-9_]
\W\w的反义 等价 [^A-Za-z0-9_]
\uhhhh匹配与值 hhhh(四个十六进制数字)对应的 UTF-16 代码单元

捕获、反向引用和零宽断言

()捕获组
(?x)具名捕获组
\n对第n个捕获组反向引用
(?:x)非捕获组,匹配x,但不记住x
(?=)正向向前查找(Positive Lookahead)
(?!)负向向前查找(Negtive Lookahead)
(?<=)正向向后查找(Positive Lookbehind)
(?<!)负向向后查找(Negtive Lookbehind)

普通字符

a-z A-Z 0-9 空格等
任何非 特殊字符 的字符(比如中文)
各种字节(正则匹配以字节为单位如: 匹配中文[\u4e00-\u9fa5]

实践和常见正则

  1. 匹配 url
const reg = /(f|ht){1}(tp|tps){1}://([\w-]+.)+([\w-]+)(/[\w-.?%&=]+)*/
'www.baidu.com'.match(reg)
'https://regex101.com/r/hwQjFa/1'.match(reg)
  1. 手机号换格式

将国内 11 为手机号替换为 xxx-xxxx-xxxx 格式

const reg = /(\d{3})(\d{4})(\d{4})/g

'12312341234'.replace(reg, '$1-$2-$3')
  1. 匹配 andriod 的 xml 文件中的色值匹配,如
(^#[0-9A-Fa-f]{6}$)|(^#[0-9A-Fa-f]{3,4}$)|(^#[0-9A-Fa-f]{8}$)
  1. 检查用户输入的密码是否符合以下规则:8-20位, 大写字母,小写字母,数字三种中至少包含两种

穷举

(((?=.*[A-Z])(?=.*[a-z]))|((?=.*[a-z])(?=.*[A-Z]))|((?=.*[A-Z])(?=.*[0-9]))|((?=.*[0-9])(?=.*[A-Z]))|((?=.*[a-z])(?=.*[0-9]))|((?=.*[0-9])(?=.*[a-z]))|((?=.*[A-Z])(?=.*[a-z])(?=[0-9]))|((?=.*[A-Z])(?=.*[0-9])(?=[a-z]))|((?=.*[a-z])(?=.*[A-Z])(?=[0-9]))|((?=.*[a-z])(?=.*[0-9])(?=[A-Z]))|((?=.*[0-9])(?=.*[A-Z])(?=[a-z]))|((?=.*[0-9])(?=.*[a-z])(?=[A-Z])))^[a-zA-Z0-9]{8,20}$
  1. 匹配四字单词,要求 abac
const reg = /(\w)\w\1\w/

const reg2 = /(\w){1}\w{1}\1{1}\w{1}/
  1. 匹配叠字
const reg = /(0-9a-zA-z)\1/
  1. key_facebook_id 替换为 keyFacebookId
const reg = /\w+(_[a-z]+)+\w+/
  1. 删除代码中的注释
第一种: 
/**
* 关注
*/
第二种: 
// xxxxxxxxx


(/**[\s\S]*?*/)|(//.*)
(/**[\u0000-\uffff]*?*/)|(//.*)
  1. 匹配 IP 地址

我们进行一些日志分析的时候,很有可能要对其中的一些 IP 地址进行识别和提取。我们最直接想到的 IP 地址匹配代码为:

\d{,3}.\d{,3}.\d{,3}.\d{,3}

这样写不够简洁,我们还可以这么写:

\d{,3}(.\d{,3}){3}

但是,这样写,依然会匹配上 999.999.999.999 这样的非法 IP 地址。我们需要匹配的数字需要在 0~255 之间。对于这样的一个数字匹配,我们可以这么写正则:

\d | [1-9]\d | 1\d\d | 2[0-4]\d | 25[0-5]

可以看到,匹配的意图很明显,就是匹配一位数,两位数,24 打头的三位数,以及 25 打头的三位数。我们可以把头两个简单合并下:

\d\d? | 1\d\d | 2[0-4]\d | 25[0-5]

最后,写成 IP 地址识别,就变成:

(\d\d?|1\d\d|2[0-4]\d|25[0-5])(.(\d\d?|1\d\d|2[0-4]\d|25[0-5])){3}

如果你用的正则语法支持子模式匹配,还可以这么写:

(\d\d?|1\d\d|2[0-4]\d|25[0-5])(.(?1)){3}

当然,这个正则是有缺陷的,它依然可能匹配类似 56789.123.32.123456 中的红色部分。为了解决这个问题,我们需要在头尾加上界定符:

\b(\d\d?|1\d\d|2[0-4]\d|25[0-5])(.(?1)){3}\b