全面理解正则表达式, 摆脱百度不到的烦恼
RegExp构造函数
RegExp是js内置对象用于将文本与一个模式匹配。
创建正则表达式的三种方式
以下三种表达式都会创建相同的正则表达式:
- 字面量形式
/ab+c/i;
- 首个参数为字符串模式的构造函数
new RegExp('ab+c', 'i');
- 首个参数为常规字面量的构造函数
new RegExp(/ab+c/, 'i');
正则常用方法
方法 | 描述 |
---|---|
exec | 一个在字符串中执行查找匹配的RegExp方法,它返回一个数组(未匹配到则返回 null)。 |
test | 一个在字符串中测试是否匹配的RegExp方法,它返回 true 或 false。 |
match | 一个在字符串中执行查找匹配的String方法,它返回一个数组,在未匹配到时会返回 null。 |
matchAll | 一个在字符串中执行查找所有匹配的String方法,它返回一个迭代器(iterator)。 |
search | 一个在字符串中测试匹配的String方法,它返回匹配到的位置索引,或者在失败时返回-1。 |
replace | 一个在字符串中执行查找匹配的String方法,并且使用替换字符串替换掉匹配到的子字符串。 |
split | 一个使用正则表达式或者一个固定字符串分隔一个字符串,并将分隔后的子字符串存储到数组中的 String 方法。 |
注意: 使用顺序不要搞反:
RegExp方法
指: 正则表达式的方式, 例如: /正则/.exec(‘字符串’)
String方法
指: 字符串的方法, 参数可以是正则, 例如: ‘字符串’.match(/正则/)
String.prototype.matchAll()提取指定文本
eg: 想从’game_loop1.2.1’这段字符串中提取出’gameloop’部分
const str = 'game_loop1.2.1';
// 1. matchAll返回的是一个迭代器
const iterator = str.matchAll(/[a-zA-Z]*/g)
console.log(iterator, Object.prototype.toString.call(iterator))
// expected output:
// RegExpStringIterator {}[[Prototype]]: RegExp String Iterator
// '[object RegExp String Iterator]'
// 2. 将迭代器获获到的所有匹配正则表达式的结果及分组捕获组放入数组:
const arr = [...iterator];
// 3. 拼接
const gameloop = arr.reduce((prev, item) => {
prev.push(item[0]);
return prev;
}, []).join('');
console.log(gameloop)
arr数组:
原义文本字符
指字符串原本的含义
/abc/.test('abc123') // true
// 这里的abc就是原义文本字符
// 指检测字符串中是否有'abc'这个字符串, 而不是指 有a 或者 有b 或者 有c, 也不是指 有a 且 有b 且 有c
元字符
有特殊含义的字符, 区别于原义文本字符 (共11个)
[] {} () + . | $ ^ \ * ?
语法
字符类
/[abc]/
: 将a b c视为一类(具有相同特征), 所以, 只要字符串中 有a 或 有b 或有c 即为true
/[abc]/.test(/a123/); // true
/[abc]/.test(/ab123/); // true
/[abc]/.test(/123/); // false
负向类(反向类)
/[^abc]/
: 将 不是a 或 不是b 或 不是c 视为一类, 所有, 只要字符串中有不是a b c其中任意一个的其他字符即为true
/[^abc]/.test(/a123/); // true
/[^abc]/.test(/ab123/); // true
/[^abc]/.test(/123/); // true
/[^abc]/.test(/aaaa/); // false
/[^abc]/.test(/aabbcc/); // false
范围类
- /[0-9]/: 0-9之间任意字符
- /[a-z]/: a-z之间任意小写字母
- /[A-Z]/: A-Z之间任意大写字母
注意点:
- 范围是一个闭区间;
- 可以连写: /[0-9a-zA-Z]/: 任何数字或字母都可以, 也可以分开/[0-46-9]/: 0-4 或 6-9;
- -表示范围, 右边一定要大于左边;
- [] 和 [^]的区别: 只要有字符串是xxx / 只要有字符串不是xxx;
预定义类
可直接使用的表达式
预定义类 | 等价于 | 含义 |
---|---|---|
. | [^\r\n] | 除回车和换行之外的所有字符 |
\d | [0-9] | 数字字符 |
\D | [^0-9] | 非数字字符 |
\s | [\f\n\r\t\v] | 空白字符 |
\S | [^\f\n\r\t\v] | 非空白字符 |
\w | [a-zA-Z_0-9] | 单词字符(字母、下划线、 数字) |
\W | [^a-zA-Z_0-9] | 非单词字符 |
/./.test(''); // false
/./.test(' '); // true 只要有东西就行
/./.test('\n'); // false 换行符不算内容
/\s/.test('哈哈'); // false
/\s/.test('哈\t哈'); // true
/\S/.test('哈哈'); // true
/\S/.test('哈\t哈'); // true
/\w/.test('aA_123'); // true
/\w/.test('aa+'); // true
/\w/.test('+-*'); // false
/\W/.test('aA_123'); // false
/\W/.test('aa+'); // true
/\W/.test('+-*'); // true
边界类
边界字符 | 含义 |
---|---|
^ | 以XX开头 |
$ | 以XX结尾 |
\b | 单词边界 |
\B | 非单词边界 |
/^abc/
: 以a开头, 紧接bc
/^abc/.test('abc123'); // true
/^abc/.test('a1b2c3'); // false
# 注意: 不是以abc开头的意思!
/abc$/
: 以c结尾, 前面紧接ab
/abc$/.test('123abc'); // true
/abc$/.test('1a2b3c'); // false
# 注意: 不是以abc结尾的意思!
/^abc$/
: (开头)a + b + (结尾)c
/^abc$/.test('abc'); // true
/^abc$/.test('abcabc'); // false
# 注意: 不是以abc开头, 以abc结尾的意思!
/\b/
这里的单词边界 是指字符串中 独立的单词 而不是某一段字符中的一部分:
'This is a boy'.replace(/is/, 'ha') // Thha is a boy. 会默认替换第一个匹配的is
'This is a boy'.replace(/\bis/, 'ha') // This ha a boy. 只会匹配到单词is, 而不是This中的is
'This is a boy'.replace(/\Bis/, 'ha') // Thha is a boy. 匹配非单词的is, 和第一个效果一样
量词
量词 | 含义 |
---|---|
? | 出现0次或1次(最多出现一次) |
+ | 出现一次或多次(至少出现一次) |
* | 出现0次或多次(任意次) |
{n} | 出现n |
{n,m} | 出现n~m次 |
{n,} | 出现至少n次 |
// 有数字连续出现两次
/\d{2}/.test('a1b2'); // false
/\d{2}/.test('a12b3'); // true
// 有数字连续出现2~3次
/\d{2,3}/.test('a1b12c'); // true
/\d{2,3}/.test('a1b123c'); // true
/\d{2,3}/.test('a1b12345c'); // true 因为其中的123连续出现了3次
'123456789'.replace(/\d?/, 'a') // 'a23456789'
'123456789'.replace(/\d+/, 'a') // a
'123456789'.replace(/\d*/, 'a') // a
'x12345678'.replace(/\d*/, 'a') // ax12345678 会匹配0次,可以看成x的前面有个空'', 将''替换成a
'123456789'.replace(/\d{3}/, 'a') // a456789
'123456789'.replace(/\d{3,5}/, 'a') // a6789
'123456789'.replace(/\d{3,}/, 'a') // a
分组
() 元字符
- 将()内的内容 当成一个整体
- 提升优先级
/(abc){3}/
: 将abc视为整体, 匹配3次
/(abc){3}/.test('1abcabcabc'); // true
/(abc){3}/.test('a1bcabcabc'); // false
/abc{3}/.test('abcabcabc'); // false 不加()只能对就近的一个字符生效, 即匹配3个连续, 也就是匹配到abccc才算true
| 元字符
或运算, 会将 | 两边整体分开, 匹配左边或右边, 常搭配 ()元字符使用
/l(o|i)ve/
: 匹配love或live
// 错误写法:
/lo|ive/.exec('love'); // lo, | 会将左右两边分开,会变成匹配lo或ive
// 正确写法
/l(o|i)ve/.exec('love'); // love
()的反向引用
当使用()时, 正则会将()内匹配到的内容存放在$中, 此时的() 也被成为捕获括号。
$1 , $2 $3, $4, $5, $6, $7, $8, $9属性是包含括号子串匹配的正则表达式的静态和只读属性。
// 需求1, 替换日期格式 2021-11-20 变成 20/11/2021
const date = '2021-11-20';
date.replace(/(\d{4})-(\d{2})-(\d{2})/, '$3/$2/$1'); // 20/11/2021
RegExp.$1 // 2021
RegExp.$2 // 11
RegExp.$3 // 20
// 需求2, 取出()内的内容
const data = 'haha(123)hehe';
const _data = data.match(/\((.+)\)/);
RegExp.$1 // 123
修饰符
影响当前整个正则规则的特殊符号, 可以写多个
i
(intensity): 不区分大小写g
(global): 全局匹配, 全部匹配完m
(multiple): 检测换行符, 使用较少, 主要影响字符串的开始^与结束$边界
// m 需要和边界一起使用
const str = '我爱我的国家\n我爱我的国家\n我爱我的国家'
// 需求: 将每一行的第一个'我' 改成 '你'
str.replace(/我/, '你') // 只能替换第一个'我'
str.replace(/我/g, '你') // 会替换所有的'我'
str.replace(/^我/g, '你') // 同样只会替换第一个'我',
str.replace(/^我/gm, '你') // 成功, m会识别\n, 将\n后的第一个字符也识别成^的位置
g
在使用时挖到的关于lastIndex
的坑
文档链接
首先说下结论:
/g
在匹配时, 由于多次匹配, 会在匹配到内容后, 记录下当前匹配内容的最后一个字符串的下标lastIndex。比如:
此时lastIndex就是第一个foo的最后一个o的下标 9const regex1 = new RegExp( 'foo', 'g' ); const str1 = 'table football, foosball'; regex1.test(str1); // true console.log(regex1.lastIndex); // 9
- 当继续使用该正则进行匹配时, 会从上一次的lastIndex开始, 往后接着进行匹配
可以看到, 再次使用时, 虽然结果同样为true, 但lastIndex已为19, 说明匹配到了第二个fooregex1.test(str1); // true console.log(regex1.lastIndex); // 19
- 当匹配不到时(匹配完了) 会返回false, 并
重置
lastIndex为0regex1.test(str1); // false console.log(regex1.lastIndex); // 0
注意!
这里会发现匹配失败了, 说明此轮匹配已结束, lastIndex并重置为0了, 如果再进行匹配, 又会重新开始, 所以会出现: true true false / true true false /true true false/…反复
其他特殊字符
字符 | 含义 |
---|---|
(?:x) | 匹配 ‘x’ 但是不记住匹配项。这种括号叫作非捕获括号,使得你能够定义与正则表达式运算符一起使用的子表达式。看看这个例子 /(?:foo){1,2}/。如果表达式是 /foo{1,2}/,{1,2} 将只应用于 ‘foo’ 的最后一个字符 ‘o’。如果使用非捕获括号,则 {1,2} 会应用于整个 ‘foo’ 单词。 |
x(?=y) | 匹配’x’仅仅当’x’后面跟着’y’.这种叫做先行断言。例如,/Jack(?=Sprat)/会匹配到’Jack’仅当它后面跟着’Sprat’。/Jack(?=Sprat|Frost)/匹配‘Jack’仅当它后面跟着’Sprat’或者是‘Frost’。但是‘Sprat’和‘Frost’都不是匹配结果的一部分。 |
(?<=y)x | 匹配’x’仅当’x’前面是’y’.这种叫做后行断言。例如,/(?<=Jack)Sprat/会匹配到’ Sprat ‘仅仅当它前面是’ Jack '。/(?<=Jack|Tom)Sprat/匹配‘ Sprat ’仅仅当它前面是’Jack’或者是‘Tom’。但是‘Jack’和‘Tom’都不是匹配结果的一部分。 |
x(?!y) | 仅仅当’x’后面不跟着’y’时匹配’x’,这被称为正向否定查找。例如,仅仅当这个数字后面没有跟小数点的时候,/\d+(?!.)/ 匹配一个数字。正则表达式/\d+ |
(?<!y)x | 仅仅当’x’前面不是’y’时匹配’x’,这被称为反向否定查找。例如, 仅仅当这个数字前面没有负号的时候,/(?<!-)\d+/ 匹配一个数字。 /(?<!-)\d+/.exec(‘3’) 匹配到 “3”. /(?<!-)\d+/.exec(’-3’) 因为这个数字前有负号,所以没有匹配到。 |