简介
正则表达式 是使用单个字符串来描述,匹配一系列符合某个规则的字符串,一般用来检索、替换匹配的文本
价值
-
编辑器中,可以快速查找指定的文本
-
编辑器中,将文本格式快速批量转换,比如把若干个手机号,生日,转换格式
-
配合 find,grep 来搜索文件
-
表单提交时进行校验,如手机号,邮箱,IP 地址等
-
配置文件,如 webpack 的 loader 也支持正则
常用工具
- 可视化常看正则表达式结构: Regulex
-
正则表达式在线测试:regex101
-
正则表达式在线测试中文版:regex101-cn
创建方式
-
字面量
由斜杠包围,而不是引号包围
// /pattern/flags
/ab/g
-
构造函数
由引号包围,不包含在斜杠之间
new RegExp('ab+c', 'i'); // 字符串形式
new RegExp(/ab+c/, 'i'); // es6 加强,构造函数也可以传递正则字面量
-
工厂方法
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 全局构造函数的静态属性 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.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,分别是
- RegExp.prototype.exec
- RegExp.prototype.test
- String.prototype.search
- String.prototype.match
- String.prototype.matchAll
- String.prototype.split
- 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"]
有两个点比较有趣
- 第二个参数可以指定返回结果长度
- 使用正则表达式,返回结果会包含分隔符
'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
优化建议
-
对于重复使用的正则表达式,需要保存在变量中,避免每次创建都实时编译
-
避免回溯,尽量避免使用重复量词
-
不需要引用分组信息的时候,使用非捕获组
-
关注如何让匹配快速失败:正则表达式慢的主要原因是匹配失败过程慢
-
以简单的,必须的字符开始,应该尽可能快速测试明显不匹配的位置
-
减少分支数量,缩小分支范围。可以使用选项组或者将分组位置推后
字符
基本元字符:
| . | 默认匹配除换行符之外的任何单个字符在字符集内,点失去了它的特殊意义,并与文字点匹配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) |
普通字符
| a-z A-Z 0-9 空格等 |
|---|
| 任何非 特殊字符 的字符(比如中文) |
| 各种字节(正则匹配以字节为单位如: 匹配中文[\u4e00-\u9fa5] |
实践和常见正则
const reg = /(f|ht){1}(tp|tps){1}://([\w-]+.)+([\w-]+)(/[\w-.?%&=]+)*/
'www.baidu.com'.match(reg)
'https://regex101.com/r/hwQjFa/1'.match(reg)
- 手机号换格式
将国内 11 为手机号替换为 xxx-xxxx-xxxx 格式
const reg = /(\d{3})(\d{4})(\d{4})/g
'12312341234'.replace(reg, '$1-$2-$3')
(^#[0-9A-Fa-f]{6}$)|(^#[0-9A-Fa-f]{3,4}$)|(^#[0-9A-Fa-f]{8}$)
穷举
(((?=.*[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}$
- 匹配四字单词,要求 abac
const reg = /(\w)\w\1\w/
const reg2 = /(\w){1}\w{1}\1{1}\w{1}/
- 匹配叠字
const reg = /(0-9a-zA-z)\1/
- key_facebook_id 替换为 keyFacebookId
const reg = /\w+(_[a-z]+)+\w+/
- 删除代码中的注释
第一种:
/**
* 关注
*/
第二种:
// xxxxxxxxx
(/**[\s\S]*?*/)|(//.*)
(/**[\u0000-\uffff]*?*/)|(//.*)
- 匹配 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