# 正则表达式

# 基本匹配

/2020/.test('2020') // true
1

# 修饰符

  • g 全局搜索
  • i 不区分大小写
  • m 多行匹配
"/The/gi" => The fat cat sat on the mat.
1

# 字符组

  • [ ] 字符类,匹配方括号中包含的任意字符。
  • [^ ] 否定字符类。匹配方括号中不包含的任意字符
/[Jj]ava/.test('java') // true
/[Jj]ava/.test('Java') // true
1
2

# 字符区间

img

# 转义

特殊符号需要转移 \

# 快捷字符

  • \d:等于[0-9]
  • \D:和上面相反
  • \w:等于[a-zA-Z0-9_],注意下划线
  • \W:和上面相反
  • \s:匹配空白符(比如空格,tab、换行等)
  • \S:匹配非空白字符
  • .:任意字符

# 量词

  • ? : 0或1
  • * :>=0 等于{0,}
  • + : >=1 等于{1,}
  • {m,n}:出现次数(m <= x<= n)

# 位置

  • ^:匹配头
  • $:匹配尾
console.log('hello'.replace(/^/, '---'))   //---hello

console.log('hello'.replace(/$/, '---'))   //hello---
1
2
3
  • \b:单词边界
  • \w和\W之间的位置
  • ^与\w之间的位置
  • \w与$之间的位置

img

'xxx_love_study_1.mp4'.replace(/\b/g, '❤️') // ❤️xxx_love_study_1❤️.❤️mp4❤️
1
  • \B:非单词的边界,与\b相反
  • \w与\w之间的位置
  • \W与\W之间的位置
  • ^与\W之间的位置
  • \W与$之间的位置

img

'[[xxx_love_study_1.mp4]]'.replace(/\B/g, '❤️')

//❤️[❤️[x❤️x❤️x❤️_❤️l❤️o❤️v❤️e❤️_❤️s❤️t❤️u❤️d❤️y❤️_❤️1.m❤️p❤️4]❤️]❤️
1
2
3

# 分组

# 分组引用

/*
提取年月日
2021-08-14
*/

let reg = /(\d{4})-(\d{2})-(\d{2})/

console.log('2021-08-14'.match(reg))
//  ["2021-08-14", "2021", "08", "14", index: 0, input: "2021-08-14", groups: undefined]

// 第二种解法,通过全局的$1...$9读取 引用的括号数据
let reg = /(\d{4})-(\d{2})-(\d{2})/
let string = '2021-08-14'

reg.test(string)

console.log(RegExp.$1) // 2021
console.log(RegExp.$2) // 08
console.log(RegExp.$3) // 14
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

#

# 分组回溯引用

除了通过js引用分组的内容,也可以通过正则来引用分组内容

let regex = /(\w+).*?</\1>/g

let string1 = "<div></div>";
let string2 = "<div></p>";

console.log( regex.test(string1) ); // true
console.log( regex.test(string2) ); // false
1
2
3
4
5
6
7

#

# 非捕获性分组

有时候,我们并不需要捕获某个分组的内容,但是又想使用分组的特性。

非捕获性括号 (?:表达式)

// 非捕获型引用
let reg = /(?:ab)+/g
console.log('ababa abbb ababab'.match(reg)) // ["abab", "ab", "ababab"]
1
2
3

# 或者

使用分组的同时还可以使用 或者(or)条件。

# 断言

?= 正先行断言-存在
?! 负先行断言-排除
?<= 正后发断言-存在
?<! 负后发断言-排除
'xxx_love_study_1.mp4'.replace(/(?=xxx)/g, '❤️') // ❤️xxx_love_study_1.mp4

'xxx_love_study_1.mp4'.replace(/(?!xxx)/g, '❤️') 
//x❤️x❤️x❤️_❤️l❤️o❤️v❤️e❤️_❤️s❤️t❤️u❤️d❤️y❤️_❤️1❤️.❤️m❤️p❤️4❤️

'xxx_love_study_1.mp4'.replace(/(?<=xxx)/g, '❤️') //xxx❤️_love_study_1.mp4

'xxx_love_study_1.mp4'.replace(/(?<!xxx)/g, '❤️') 
//❤️x❤️x❤️x_❤️l❤️o❤️v❤️e❤️_❤️s❤️t❤️u❤️d❤️y❤️_❤️1❤️.❤️m❤️p❤️4❤️
1
2
3
4
5
6
7
8
9

# 样例

数字的千分位分割法

let price = '123456789'
let priceReg = /(?!^)(?=(\d{3})+$)/g

console.log(price.replace(priceReg, ',')) // 123,456,789
1
2
3
4

手机号3-4-4分割

let mobile = '18379836654'
let mobileReg = /(?=(\d{4})+$)/g

console.log(mobile.replace(mobileReg, '-')) // 183-7983-6654
1
2
3
4

# 案例

匹配id

// 1
let regex = /id=".*?"/ // 想想为什么要加? 不加的话 连后面的class都会匹配到
let string = '<div id="container" class="main"></div>';
console.log(string.match(regex)[0]);
// 2
let regex = /id="[^"]*"/ 
let string = '<div id="container" class="main"></div>'; 
console.log(string.match(regex)[0]); 
1
2
3
4
5
6
7
8

匹配颜色

// 要求匹配如下颜色
/*
#ffbbad
#Fc01DF
#FFF
#ffE
*/

let regex = /#([a-fA-F\d]{6}|[a-fA-F\d]{3})/g
let string = "#ffbbad #Fc01DF #FFF #ffE";

console.log(string.match(regex))
//  ["#ffbbad", "#Fc01DF", "#FFF", "#ffE"]
1
2
3
4
5
6
7
8
9
10
11
12
13

匹配时间

/*
    要求匹配
  23:59
  02:07
*/
// 解析:
// 第一位:可以是0、1、2
// 第二位:当第一位位0或者1的时候,可以是0到9、第一位是2的时候,只可以是0到3
// 第三位:固定是冒号:
// 第四位:可以是0到5
// 第五位:0到9

let regex = /^([01]\d|2[0-3]):[0-5]\d$/

console.log(regex.test('23:59')) // true
console.log(regex.test('02:07'))// true

// 衍生题,可以是非0
let regex = /^(0?\d|1\d|2[0-3]):(0?|[1-5])\d/

console.log( regex.test("23:59") ) // true
console.log( regex.test("02:07") ) // true
console.log( regex.test("7:09") ) // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

匹配日期

/*
    要求匹配
  23:59
  02:07
*/
// 解析:
// 第一位:可以是0、1、2
// 第二位:当第一位位0或者1的时候,可以是0到9、第一位是2的时候,只可以是0到3
// 第三位:固定是冒号:
// 第四位:可以是0到5
// 第五位:0到9

let regex = /^([01]\d|2[0-3]):[0-5]\d$/

console.log(regex.test('23:59')) // true
console.log(regex.test('02:07'))// true

// 衍生题,可以是非0
let regex = /^(0?\d|1\d|2[0-3]):(0?|[1-5])\d/

console.log( regex.test("23:59") ) // true
console.log( regex.test("02:07") ) // true
console.log( regex.test("7:09") ) // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 懒惰匹配

正则表达式通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符。比如这个表达式:a.*b,它将会匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab的话,它会匹配整个字符串aabab,这被称为贪婪匹配

懒惰匹配,也就是匹配尽可能少的字符。在能使整个匹配成功的前提下使用最少的重复,只要在它后面加上一个问号?即可。现在看看懒惰版的例子吧:

a.*?b匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aab(第一到第三个字符)和ab(第四到第五个字符)

*? 
+?
??
{n,m}?
{n,}?
1
2
3
4
5

# RegExp对象

# 定义

var regex = /xyz/; //字面量定义和下面的相等,该定义方式更高效

var regex = new RegExp('xyz');
1
2
3

斜杠 "/" 会告诉 JavaScript 我们正在创建一个正则表达式。它的作用类似于字符串的引号。new RegExp 可以动态传入参数创建正则。

# 实例方法

# test()

test方法返回一个布尔值,表示当前模式是否能匹配参数字符串

/cat/.test('cats and dogs') // true
1

# exec()

exec()方法,用来返回匹配结果。如果发现匹配,就返回一个数组,成员是匹配成功的子字符串,否则返回null。

var s = '_x_x';
var r1 = /x/;
var r2 = /y/;

r1.exec(s) // ["x"]
r2.exec(s) // null
1
2
3
4
5
6

# String实例方法

# match()

match()方法的唯一参数就是一个正则表达式,返回的是一个由匹配结果组成的数组。如果该正则表达式设置了修饰符g,则该方法返回的数组包含字符串的所有匹配结果。

"1 plus 2 equals 3".match(/\d+/g)
// 返回 ["1","2","3"]
1
2

如果没有设置修饰符g,那么就不会进行全局检索,只检索第一个匹配。但是即使match()执行的不是全局检索,也返回一个数组。这时,数组的第一个元素就是匹配的字符串,余下的元素则是正则表达式中用圆括号括起来的子表达式。如果没有,则返回null。

"1 plus 2 equals 3".match(/\d+/)
// [ '1', index: 0, input: '1 plus 2 equals 3', groups: undefined ]
1
2

search()的参数是一个正则表达式,返回第一个与之匹配的子串的起始位置,如果找不到匹配的子串,它将返回-1。

"JavaScript".search(/script/i);
1

如果search()的参数不是正则表达式,首先会通过 RegExp 构造函数将它转换为正则表达式,search()方法不支持全局检索,因为会忽略正则表达式参数中的修饰符g。

#

# replace()

该方法用以执行检索和替换操作。其中第一个参数是一个正则表达式,第二个参数是要进行替换的字符串。

如果正则表达式中设置了修饰符 g,那么源字符串中所有与模式匹配的子串都将替换成第二个参数指定的字符串;如果不带修饰符g,则只替换所匹配的第一个子串。

如果 replace() 的第一个参数是字符串而不是正则表达式,那么replace()将直接搜索这个字符串,而不会向search()一样先转换为正则表达式。

str.replace(/javascript/gi,"JavaScript");
//将所有不区分大小写的javascript替换成JavaScript
1
2

yyyy-mm-dd 格式,替换成 mm/dd/yyyy

var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
var result = string.replace(regex, "$2/$3/$1");
console.log(result)
1
2
3
4

使用函数

function replacer(match, p1, p2, p3, offset, string) {
  console.log(match); //abc12345#$*%
  return [p1, p2, p3].join(' - ');
}
var newString = 'abc12345#$*%'.replace(/([^\d]*)(\d*)([^\w]*)/, replacer);
console.log(newString);  // abc - 12345 - #$*%
1
2
3
4
5
6

# 参考

# 工具

https://regexper.com/ (opens new window)

ttps://regexr-cn.com/ (opens new window)

https://jex.im/regulex/ (opens new window)

https://regex101.com/ (opens new window)

# 正则案例

# 字符串 trim 方法模拟

trim 方法是去掉字符串的开头和结尾的空白符

function trim(str) {
	return str.replace(/^\s+|\s+$/g, ''); // 替换开头和结尾的空白符
}

function trim (str) {
	return str.replace(/^\s*(.*?)\s*$/g, "$1");// 匹配整个字符串,然后用引用来提取出相应的数据
}
//这里使用了惰性匹配 *?,不然也会匹配最后一个空格之前的所有空格的。
1
2
3
4
5
6
7
8

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

function titleize(str) {
   /\b\w/g 也可
  return str.replace(/(^|\s)\w/g, function (c) {
    return c.toUpperCase();
  });
}
1
2
3
4
5
6

# 驼峰化

function camelize(s) {
    return s.replace(/-\w/g, function(x) {
        return x.slice(1).toUpperCase();
    })
}
1
2
3
4
5

# 中划线化

function dasherize (str) {
return str.replace(/([A-Z])/g, '-$1').replace(/[-_\s]+/g, '-').toLowerCase();
}
1
2
3