
Get Started
JS 中创建一个正则表达式有两种方式:
-
使用一个正则表达式字面量,其由包含在斜杠之间的模式组成,如下所示:
var re = /ab+c/; -
或者通过
RegExp构造函数,new一个正则表达式:const reg = new RegExp('ab+c'); -
使用正则表达式
const reg = new RegExp('abc'); const str = 'helloabcword'; reg.test(str); reg.exec(str); str.match(reg); str.matchAll(reg); str.replace(reg, ' '); str.search(reg); str.split(reg);
简单模式
简单模式是直接找到你想匹配的结构。/abc/ 能匹配 helloabc1231abckj字符串中的所有abc,匹配要求字符及其顺序一样,也就是说/abc/只能匹配abc,不能匹配 bca,bac。
const reg = /abc/;
// 检测某个字符串是否匹配某个正则
reg.test('abc'); //true
reg.test('abcdef'); //true
reg.test('12313abcdef'); //true
reg.test('12313ab1cdef'); //false
特殊字符
单纯地匹配某一种固定的字符结构,其能实现的功能很有限,所以正则表达式还提供很多特殊字符,借助这些特殊字符能实现丰富的匹配。按照其功能可划分为:字符集、字符类、组、量词、断言。需要注意一点,特殊字符都已经具备了特殊的功能,当要匹配特殊字符本身时,需要转义,比如要匹配\d时,\d是特殊字符需要转义,正则应该为/\\d/。
字符集
当需要匹配多种可能时,比如要匹配a或b字符,可以使用管道|或者字符集[]。
|与 JS 中的||类似,表示两个之间的一个。[]表示字符集或范围,在中括号里面的字符,或者在中括号所描述字符集的范围里面的都会被匹配。/a|b|c/或/[abc]/都是匹配a或b或c,当要匹配的是所有数字或者所有字母,是否需要把所有的数字罗列出来,像这样/[0123456789]/?
其实是有便捷的写法的:
/[0-9]/与/[0123456789]/的效果完成一致,匹配所有数字。/[a-z]/匹配所有小写字母。/[A-Z]/匹配所有大写字母。/[a-zA-Z]/匹配所有字母。/[a-zA-Z0-9]/匹配所有的字母和数字。/[a-zA-Z0-9_]/匹配字母数字和下划线,与/\w/效果一致,\w是特殊字符,下文详讲。/[^a-zA-Z]/匹配非字母字符, 此处的^表示非,即匹配不包含在中括号中的字符。
需要注意一点,一些特殊字符在字符集中不再是特殊字符,比如,/[b|a]/表示匹配b、|和a这三个字符。
const str = 'Hi, your 6 apples is 10_$';
// g表示标志,即会查询整个字符串,不加的时候在找到第一个符合的字符时即会停止查找,下文详讲
const reg1 = /a|b|c|d/g;
console.log(str.match(reg1)); //[ 'a' ]
const reg2 = /[abcd]/g;
console.log(str.match(reg2)); //[ 'a' ]
const reg3 = /[0-9]/g;
console.log(str.match(reg3)); // [ '6', '1', '0' ]
const reg4 = /[a-z]/g;
console.log(str.match(reg4)); // [ 'i', 'y', 'o', 'u', 'r', 'a', 'p', 'p', 'l', 'e', 's', 'i', 's' ]
const reg5 = /[A-Z]/g;
console.log(str.match(reg5)); // [ 'H' ]
const reg6 = /[0-9a-zA-Z_]/g;
console.log(str.match(reg6));
//['H', 'i', 'y', 'o', 'u', 'r', '6', 'a', 'p', 'p', 'l', 'e', 's', 'i', 's', '1', '0', '_'];
const reg7 = /\w/g;
console.log(str.match(reg7));
//['H', 'i', 'y', 'o', 'u', 'r', '6', 'a', 'p', 'p', 'l', 'e', 's', 'i', 's', '1', '0', '_'];
const reg8 = /[^a-zA-Z]/g;
console.log(str.match(reg8));
// [ ',', ' ', ' ', '6', ' ', ' ', ' ', '1', '0', '_', '$' ]
字符类
字符类是匹配某一种类字符的特殊字符。
. 匹配单个任意字符
.匹配除行终止符(\n、\r、\u2028、\u2029)之外的单个任意字符,但是在字符集中时,它仅匹配.,也就是说在字符集中.不再是特殊字符。
const reg = /./g;
const str = '1aA./|`\n1a,';
console.log(str.match(reg));
// [ '1', 'a', 'A', '/', '|', '`', '1', 'a', ',' ]
const reg1 = /\./g;
console.log(str.match(reg1)); // [ '.' ]
const reg2 = /[.]/g;
console.log(str.match(reg2)); // [ '.' ]
\d 匹配单个数字
\d匹配单个数字,d即 digital 的缩写,等价于[0-9]。
const str = 'zd1`2asd123';
const reg = /\d/g;
const reg1 = /[0-9]/g;
console.log(str.match(reg)); // [ '1', '2', '1', '2', '3' ]
console.log(str.match(reg1)); // [ '1', '2', '1', '2', '3' ]
\D 匹配单个非数字
\D匹配单个非数字字符,与[^0-9]等价
const str = 'zd1`2asd123';
const reg = /\D/g;
const reg1 = /[^0-9]/g;
console.log(str.match(reg)); // [ 'z', 'd', '`', 'a', 's', 'd' ]
console.log(str.match(reg1)); // [ 'z', 'd', '`', 'a', 's', 'd' ]
\w 匹配单个字母数字下划线
\w匹配单个字符数字下划线字符,w可记为 word 的缩写,与[0-9a-zA-Z_]等价
const str = 'zd1`As@_d)123';
const reg = /\w/g;
const reg1 = /[0-9a-zA-Z_]/g;
console.log(str.match(reg));
// [ 'z', 'd', '1', 'A', 's', '_', 'd', '1', '2', '3' ]
console.log(str.match(reg1));
// [ 'z', 'd', '1', 'A', 's', '_', 'd', '1', '2', '3' ]
\W 匹配单个非字母数字下划线
\W匹配单个非数字字母下划线字符,与[^0-9a-zA-Z_]等价。
const str = 'zd1As@_d)123';
const reg = /\W/g;
const reg1 = /[^0-9a-zA-Z_]/g;
console.log(str.match(reg)); // [ '@', ')' ]
console.log(str.match(reg1)); //[ '@', ')' ]
非打印字符
非打印字符也可以是正则的组成部分,这些非打印字符包括:
\cx匹配由x指明的单个控制字符,如\cM匹配一个Control-M或回车符。详情查看控制字符。\f匹配单个换页符, 等价于\x0c和\cL。\n匹配单个换行符,等价于\x0a和\cJ。\r匹配一个回车符,等价于\x0d和\cM。\t匹配单个制表符,等价于\x09和\cI。\v匹配一个垂直制表符, 等价于\x0b和\cK。
const str = `
`;
const str1 = '\t';
const reg = /\n/g;
const reg1 = /\t/g;
console.log(str.match(reg)); // [ '\n', '\n' ]
console.log(str1.match(reg1)); //[ '\t' ]
\s 匹配空格
\s匹配单个空格字符,空格字符包括,空格、tab 制表符、换页符、换行符、回车符和 Unicode 空白字符,等价于[\f\n\r\t\vu00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]。
const str = `
`;
const str1 = '\t\v12\r\n';
const reg = /\s/g;
console.log(str.match(reg)); // [ '\n', '\n' ]
console.log(str1.match(reg)); //[ '\t', '\u000b', '\r', '\n' ]
console.log('\v'.charCodeAt(0).toString(16)); // b
\S 匹配非空格
\S匹配单个非空格字符,等价于[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]。
const str1 = '\t\v12\r\n';
const reg = /\S/g;
console.log(str1.match(reg)); // [ '1', '2' ]
\字符
\字符根据其后面跟着的字符有两种含义:
- 当其后面跟着一个普通字符时,有可能与后面跟着的普通字符一起构成特殊字符,比如
\d,\与d一起构成了数字特殊字符。 - 当其后面跟着一个特殊字符时,会将后面的特殊字符转义,将其变成普通字符,如
\*,\将*这个特殊字符转义,使其变成普通字符,匹配*这个符号。
const str = 'aas(das*[\\';
const reg = /\*/g;
const reg1 = /\w/g;
const reg2 = /\\/g;
console.log(str.match(reg)); // [ '*' ]
console.log(str.match(reg1)); // [ 'a', 'a', 's', 'd', 'a', 's' ]
console.log(str.match(reg2)); // [ '\\' ]
捕获组和非捕获组
当要匹配两个字符串中的一个(比如要匹配 hello 或者 word 字符串)时应该怎么办?这个时候可以使用捕获组(x) 或者非捕获组(?:x)。
-
捕获组和非捕获组的区别
这两者的区别在于捕获组会记住组匹配项,引起性能的损失,当不需要取到组内的匹配内容时应尽可能使用非捕获组。
const str = 'My first program : Hello World'; const reg1 = /(Hello)|(World)/g; console.log(str.match(reg1)); // [ 'Hello', 'World' ] const reg2 = /(?:Hello)|(?:World)/g; console.log(str.match(reg2)); // [ 'Hello', 'World' ]从上面的结果看,捕获组和非捕获组似乎没有区别?这是因为使用
g全局匹配的标志,match 函数在全局匹配的时候是不会返回捕获组信息的,可以使用matchAll替代。下面试一试在非全局匹配的情况下:const reg3 = /(Hello) (World)/; console.log(str.match(reg3)); // ['Hello World','Hello','World',(index: 19),(input: 'My first program : Hello World'),(groups: undefined)]; const reg4 = /(?:Hello) (?:World)/; console.log(str.match(reg4)); // [ 'Hello World',index: 19,input: 'My first program : Hello World',groups: undefined ]可以看到
reg3出了返回完整的匹配字符串Hello World,还返回了捕获组Hello和World这两个子串,而reg4则仅仅返回了完整的字符串。 -
具名捕获组
当有多个捕获组,而且捕获组还嵌套的情况下,
match返回的结果是按其顺序的,这样子在取某个组的匹配结果会很不方便,这个时候可以使用命名捕获组(?<name>)。看以下示例:// 取出QQ邮箱前面的QQ号,可以使用零宽断言,这里使用捕获组 const str2 = '112123232@qq.com'; const reg5 = /(.+)(@(.+))/; console.log(str2.match(reg5)); // [ '112123232@qq.com','112123232','@qq.com','qq.com',index: 0,input: '112123232@qq.com',groups: undefined ] const reg6 = /(?<code>.+)(?<suffix>@(?<domain>.+))/; console.log(str2.match(reg6).groups); // { code: '112123232', domain: 'qq.com', suffix: '@qq.com' } -
组匹配结果反向引用
假如有一段字符串
<div>div的值<div><span>span的值<span>,开始标签和结束标签是一样的,中间则是它的内容,如何取出div的值和span的值这两个值,这个时候就需要使用\n特殊字符了,其中n是一个大于 0 的整数,\n会反向引用第n个组匹配的结果,下面看实例:const str = '<div>12313<div><span>789<span>'; const reg = /(<(\w+)>)(.+)\1/g; // \1 引用第一个组 <(\w+)>的匹配内容, // 在匹配<div>12313<div>时, /(<(\w+)>)(.+)\1/相当变成了<div>(.+)<div> // 而在匹配<span>789<span>, /(<(\w+)>)(.+)\1/会变成<span>(.+)<span> let res; while ((res = reg.exec(str))) { console.log(res); } // ['<div>12313<div>', '<div>', 'div', '12313']; // ['<span>789<span>', '<span>', 'span', '789'];
量词
前面介绍的无论是字符集,还是字符类和组都是匹配单个数量的,当我们需要匹配多个项(字符集、字符类或者组),比如要一次匹配多个连续数字的时候应该怎么办?这就需要用到量词,量词用以表示要匹配的字符或者表达式的数量。
* 匹配任意数量字符
*字符表示匹配任意数量的字符或表达式。
const reg = /a*/g;
// 匹配1时,0个数量的a,所以返回‘’,匹配2时也一样
console.log('12aa45aa'.match(reg)); // [ '', '', 'aa', '', '', 'aa', '' ]
const reg1 = /\w*/g;
console.log('ui1213m'.match(reg1)); // [ 'ui1213m', '' ]
const reg2 = /\d*/g;
console.log('a1231ad12'.match(reg2)); // [ '', '1231', '', '', '12', '' ]
const reg3 = /(\$\d)*/g; // 匹配$+一个数字,并且是匹配任意次,形如 $1;
console.log('1$1$3'.match(reg3)); // [ '', '$1$3', '' ]
const reg4 = /[1-5a]*/g;
console.log('yu8912a1234'.match(reg4)); // [ '', '', '', '', '12a1234', '' ]
以12aa45aa要匹配/a*/g为例,从第一个字符1开始匹配,匹配 0 个a,返回空字符, 一直到aa,匹配两个a。
+ 匹配一个&以上
+匹配一个或多个字符或表达式.
const reg = /a+/g;
console.log('12aa45aa'.match(reg)); // [ 'aa', 'aa' ]
const reg1 = /\w+/g;
console.log('ui1213m'.match(reg1)); // [ 'ui1213m' ]
const reg2 = /\d+/g;
console.log('a1231ad12'.match(reg2)); // [ '1231', '12' ]
const reg4 = /[1-5a]+/g;
console.log('yu8912a1234'.match(reg4)); // [ '12a1234' ]
? 匹配一个&以下
?字符匹配 0 个或 1 个字符或表达式。
const reg = /a?/g;
console.log('12aa45aa'.match(reg));
//[ '', '', 'a', 'a', '', '', 'a', 'a', '' ]
const reg1 = /\w?/g;
console.log('ui1213m'.match(reg1));
// [ 'u', 'i', '1', '2', '1', '3', 'm', '' ]
const reg2 = /\d?/g;
console.log('a1231ad12'.match(reg2));
// [ '', '1', '2', '3', '1', '', '', '1', '2', '' ]
const reg4 = /[1-5a]?/g;
console.log('yu8912a1234'.match(reg4));
// [ '', '', '', '', '1', '2', 'a', '1', '2', '3', '4', '' ]
{n} 匹配 n 个
{n}表示匹配n个字符或表达式。
const reg = /a{2}/g;
console.log('12aa45aa'.match(reg)); //[ 'aa', 'aa' ]
const reg1 = /\w{3}/g;
console.log('ui1213m'.match(reg1)); //[ 'ui1', '213' ]
const reg2 = /\d{3}/g;
console.log('a1231ad12'.match(reg2)); // [ '123' ]
const reg4 = /[1-5a]{4}/g;
console.log('yu8912a1234'.match(reg4)); //[ '12a1' ]
{n,} 匹配 n 个以上
{n,} 表示至少匹配字符或表达式n次。
const reg = /a{2,}/g;
console.log('12aa45aa'.match(reg)); //[ 'aa', 'aa' ]
const reg1 = /\w{3,}/g;
console.log('ui1213m'.match(reg1)); //[ 'ui1213m' ]
const reg2 = /\d{3,}/g;
console.log('a1231ad12'.match(reg2)); // [ '1231' ]
const reg4 = /[1-5a]{4,}/g;
console.log('yu8912a1234'.match(reg4)); //[ '12a1234' ]
{n,m}匹配 n-m 个
{n,m}表示匹配某个字符或表达式 n-m 次, n 为大于或等于 0 的整数,m 为大于 0 的整数。
const reg = /a{1,5}/g;
console.log('12aa45aa'.match(reg)); //[ 'aa', 'aa' ]
const reg1 = /\w{3,5}/g;
console.log('ui1213m'.match(reg1)); //[ 'ui121' ]
const reg2 = /\d{1,5}/g;
console.log('a1231ad12'.match(reg2)); // [ '1231', '12' ]
const reg4 = /[1-5a]{4,9}/g;
console.log('yu8912a1234'.match(reg4)); //[ '12a1234' ]
非贪婪模式
以上介绍的几个量词都是”贪婪的“, 贪婪的意思是它会尽可能地匹配更多的字符,如"aaaaaaaa".match(/a{2,5}/),其结果会返回[ 'aaaaa' ], 正则允许匹配 2-5 个 a,它会在满足正则的情况会匹配尽可能多的 a,但又一些情况我们需要让它非贪婪,让它在满足正则之后就进入下一次匹配,这种情况可在量词后面加一个?,表示进行非贪婪匹配。
console.log('aa45aa'.match(/a*/g)); //[ 'aa', '', '', 'aa', '' ]
console.log('aa4aa'.match(/a*?/g)); //[ '', '', '', '', '', '' ]
console.log('ui1213m'.match(/\w+/g)); //[ 'ui1213m' ]
console.log('ui1213m'.match(/\w+?/g)); //[ 'u', 'i', '1', '2', '1', '3', 'm' ]
console.log('1a12'.match(/\d?/g)); // [ '1', '', '1', '2', '' ]
console.log('1a12'.match(/\d??/g)); // [ '', '', '', '', '' ]
console.log('helloo'.match(/o{2}/g)); // [ 'oo' ]
console.log('helloo'.match(/o{2}?/g)); // [ 'oo' ]
console.log('aaaaaaaa'.match(/a{2,5}/g)); // [ 'aaaaa', 'aaa' ]
console.log('aaaaaaaa'.match(/a{2,5}?/g)); // [ 'aa', 'aa', 'aa', 'aa' ]
标志(flags)
前文在进行测试时经常会在正则的后面加一个g,比如/a{2}/g, 那么这个g表示什么呢?这个g是正则表达式的参数,表示全局搜索,即会完整的搜索整个字符串,而不是找到第一个满足要求的子串就停止匹配。除了g,正则表达式还提供了另外 5 个可选参数 (flags) 允许全局和不分大小写搜索等。这些参数既可以单独使用也能以任意顺序一起使用, 并且被包含在正则表达式实例中。
| 标志 | 描述 |
|---|---|
| g | 全局搜索。 |
| i | 不区分大小写搜索。 |
| m | 多行搜索, ^也可以匹配换行符的开头, $也可以匹配行符的结尾 |
| s | 允许 . 匹配换行符。 |
| u | 使用 unicode 码的模式进行匹配。 |
| y | 执行“粘性(sticky)”搜索,匹配从目标字符串的当前位置开始。 |
console.log('abcABC'.match(/abc/g)); // [ 'abc' ]
console.log('abcABC'.match(/abc/gi)); // [ 'abc', 'ABC' ]
console.log('abc\nABC'.match(/^\w+$/g)); // null
console.log('abc\nABC'.match(/^\w+$/gm)); //[ 'abc', 'ABC' ]
零宽断言
零宽断言的组成之一是边界,匹配文本,单词或模式的边界,它本身并不会匹配任何东西,即0宽度。
^ 匹配字符串的开头
当需要判断某个字符串是否以某个字母开头时变需要用到^,需要注意的是,在多行匹配模式(即 flags 为 m)下,^也匹配换行符之后。
let fruits = ['Apple', 'Watermelon', 'Orange', 'Avocado', 'Strawberry'];
// 使用正则 /^A/ 过滤出以'A'开头的水果.
let fruitsStartsWithA = fruits.filter((fruit) => /^A/.test(fruit));
console.log(fruitsStartsWithA); // [ 'Apple', 'Avocado' ]
// 使用正则 /^[^A]/ 选择 不是以 ‘A’ 开头的水果
// 1) 第一个^匹配输入的开头
// 2) 字符集中的^为‘非’的意思, [^A]意思是匹配不是 ‘A’ 的字符
let fruitsStartsWithNotA = fruits.filter((fruit) => /^[^A]/.test(fruit));
console.log(fruitsStartsWithNotA); // [ 'Watermelon', 'Orange', 'Strawberry' ]
const str = 'hello world\nhello my world';
console.log(str.match(/^hello/g)); // [ 'hello' ]
// 多行模式下 ^也匹配换行符的结尾
console.log(str.match(/^hello/gm)); // [ 'hello', 'hello' ]
$ 匹配字符串的结尾
$用于判断字符串是否以某个字符串结尾,与^类似,在多行模式下,$也匹配换行符的开头。
let fruits = ['Apple', 'Watermelon', 'Orange', 'Avocado', 'Strawberry'];
// 使用正则 /o$/ 过滤出以'o'结尾的水果.
let fruitsStartsWithA = fruits.filter((fruit) => /o$/.test(fruit));
console.log(fruitsStartsWithA); // [ 'Avocado' ]
// 筛选出不以‘o’结尾的水果
let fruitsStartsWithNotA = fruits.filter((fruit) => /[^o]$/.test(fruit));
console.log(fruitsStartsWithNotA); // [ 'Apple', 'Watermelon', 'Orange', 'Strawberry' ]
const str = 'hello world\nhello my world';
console.log(str.match(/world$/g)); // [ 'world' ]
// 多行模式下 $也匹配换行符的开头
console.log(str.match(/world$/gm)); // [ 'world', 'world' ]
// 筛选出既以A开头,又以o结尾的水果
console.log(fruits.filter((fruit) => /^A\w*o$/.test(fruit))); // [ 'Avocado' ]
\b 匹配单词的边界
匹配单词的左右边界,这是一个前或者后没有另外一个字符的位置,例如字母和空格之间。
/\bm/在 "moon" 中匹配到 "m"/oo\b/在 "moon" 中不会匹配到 "oo", 因为 "oo" 后面跟着 "n" 这个单词字符./oon\b/在 "moon" 中匹配 "oon", 因为 "oon" 是这个字符串的结尾, 因此后面没有单词字符/\w\b\w/将永远不会匹配任何东西,因为一个单词字符后面永远不会有非单词字符和单词字符
let fruitsWithDescription = ['Red apple', 'Orange orange', 'Green Avocado'];
// 选择包含以 “en” 或 “ed” 结尾的单词的描述:
let enEdSelection = fruitsWithDescription.filter((descr) => /(en|ed)\b/.test(descr));
console.log(enEdSelection); // [ 'Red apple', 'Green Avocado' ]
\B 匹配单词非边界
匹配非单词边界,这是上一个字符和下一个字符属于同一类型(字母与数字时同一类型,字母与空格是不同类型)的位置:要么两者都必须是单词,要么两者都必须是非单词,例如在两个字母之间或两个空格之间。
let values = ['Red apple', 'ppOrange orangepp', 'Greenpp Avocado'];
// 筛选出单词中间存在“pp”的项,
const result = values.filter((val) => /\Bpp\B/.test(val));
console.log(result); // [ 'Red apple' ]
const str = 'hello1 word, llo 1231';
console.log(str.match(/llo/g)); // [ 'llo', 'llo' ]
console.log(str.match(/llo\B/g)); // [ 'llo' ]
x(?=y) 前向断言
x(?=y)匹配x后面跟着y的x,匹配的值是x.
// 匹配First后面跟着“ test”的First
let regex = /First(?= test)/g;
console.log('First test'.match(regex)); // [ 'First' ]
// First后面不跟着“ test”,故返回null
console.log('test First peach'.match(regex)); // null
console.log('This is a First test in a year.'.match(regex)); // [ 'First' ]
console.log('This is a First peach in a month.'.match(regex)); // null
x(?!y) 前向否定断言
x(?!y)匹配x后面不跟着y的x,匹配的值是x.
// 匹配First后面不跟着“ test”的First
let regex = /First(?! test)/g;
// First后面跟着“ test”,故返回null
console.log('First test'.match(regex)); // null
// First后面不跟着“ test”,故返回First
console.log('test First peach'.match(regex)); // [ 'First' ]
console.log('This is a First test in a year.'.match(regex)); // null
console.log('This is a First peach in a month.'.match(regex)); //[ 'First' ]
(?<=y)x 后向断言
(?<=y)x匹配x前面是y的x, 匹配的结果是x,这里的x和y都表示的是字符或正则表达式。
let oranges = ['69 ripe orange z ', 'green orange B', '78 ripe orange A '];
// 找出 '69 ripe orange A ' 和 '78 ripe orange A '
const result = oranges.filter((fruit) => /(?<=ripe\s)orange/.test(fruit));
// /(?<=ripe\s)orange/ 表示匹配 ripe\s 后面跟着orange的字符串
console.log(result); // [ '69 ripe orange z ', '78 ripe orange A ' ]
// vue 模板中嵌入字符串为 {{name}}, 那么如何取到name
const str = '名字: {{name}};年龄:{{age}}';
// 可以前向断言和后向断言结合使用,匹配前面是{{, 后面是}},中间为字符的字符串
console.log(str.match(/(?<={{)\w+(?=}})/g)); // [ 'name', 'age' ]
(?<!y)x 后向否定断言
(?<!y)x匹配x前面不是y的x.
let oranges = ['69 ripe orange z ', '99 green orange B', '78 ripe orange A '];
// 找出 '99 green orange B'
const result = oranges.filter((fruit) => /(?<!ripe\s)orange/.test(fruit));
// /(?<!ripe\s)orange/ 表示匹配 orange 前面不跟这个ripe\s的orange,
// '99 green orange B' 中存在这样的字符,故被筛选出来
console.log(result); // [ '99 green orange B' ]
//找出前面不是<且后面不是>的字母
const str = '<d>hello<d>';
console.log(str.match(/(?<!<)\w+(?!>)/g)); // [ 'hello' ]
注意: safari 浏览器不支持前向和后向断言,会直接报错。
JS 使用正则
在 JS 中使用正则主要通过借助RegExp的test和exec方法,String对象的match,matchAll,replace,search和split。
RegExp
RegExp.prototype.test()
test 用以判断字符串是否符合某一个正则,test方法时会检索整个字符串,只要字符串中存在符合正则的子串即会返回 true。
-
语法
regexObj.test(str)
-
参数
str 用来与正则表达式匹配的字符串
-
返回值
如果正则表达式与指定的字符串匹配 ,返回 true;否则 false。
console.log(/\d+/.test('¥1213')); // true
console.log(/^\d+/.test('¥1213')); // false
console.log(/^\d+/.test('1213&')); // true
console.log(/^\d+$/.test('1213&')); // false
console.log(/^\d+$/.test('1213')); // true
const str = 'hello world';
const reg = /hello/;
reg.test(str); //true
RegExp.prototype.exec()
exec 用以在一个数组中执行搜索匹配,返回匹配数组或null。在设置了 global 或 sticky 标志位的情况下(如 /foo/g or /foo/y),JavaScript RegExp 对象是有状态的。他们会将上次成功匹配后的位置记录在 lastIndex 属性中。使用此特性,exec() 可用来对单个字符串中的多次匹配结果进行逐条的遍历(包括捕获到的匹配).
-
语法
regexObj.exec(str)
-
参数
str 要匹配正则表达式的字符串。
-
返回值
如果匹配成功,exec() 方法返回一个数组(包含额外的属性 index 和 input ,参见下方表格),并更新正则表达式对象的 lastIndex 属性。完全匹配成功的文本将作为返回数组的第一项,从第二项起,后续每项都对应正则表达式内捕获括号里匹配成功的文本。
如果匹配失败,exec() 方法返回 null,并将 lastIndex 重置为 0 。
// 当使用while搜索整个字符串时,必须要用/g 全局匹配,否则会陷入死循环
const regex = /foo*/g;
const str = 'table football, foosball';
let result;
while ((result = regex.exec(str)) !== null) {
console.log(`搜索结果:${result[0]};搜索结束索引: ${regex.lastIndex}。`);
}
// 搜索结果:foo;搜索结束索引: 9。
// 搜索结果:foo;搜索结束索引: 19。
var re = /quick\s(brown).+?(jumps)/i;
var result = re.exec('The Quick Brown Fox Jumps Over The Lazy Dog');
console.log(result);
// [ 'Quick Brown Fox Jumps','Brown','Jumps',index: 4,input: 'The Quick Brown Fox Jumps Over The Lazy Dog',groups: undefined ]
String
String.prototype.match()
match 方法检索返回一个字符串匹配正则表达式的结果。
-
语法
str.match(regexp)
-
参数
regexp 一个正则表达式对象。如果传入一个非正则表达式对象,则会隐式地使用 new RegExp(obj) 将其转换为一个 RegExp 。如果你没有给出任何参数并直接使用 match() 方法 ,你将会得到一 个包含空字符串的 Array :[""] 。
-
返回值
如果使用 g 标志,则将返回与完整正则表达式匹配的所有结果,但不会返回捕获组。 如果未使用 g 标志,则仅返回第一个完整匹配及其相关的捕获组(Array)。 在这种情况下,返回的项目将具有如下所述的其他属性。
const str = 'https://www.baidu.com http://www.google.com';
console.log(str.match(/(https?):\/\//));
// [ 'https://','https',index: 0]
console.log(str.match(/(https?):\/\//g));
//[ 'https://', 'http://' ]
String.prototype.matchAll()
matchAll() 方法返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。
-
语法
str.matchAll(regexp)
-
参数
regexp 正则表达式对象。如果所传参数不是一个正则表达式对象,则会隐式地使用 new RegExp(obj) 将其转换为一个 RegExp 。
RegExp 必须是设置了全局模式 g 的形式,否则会抛出异常 TypeError。
-
返回值
一个迭代器(不可重用,结果耗尽需要再次调用方法,获取一个新的迭代器)。
const regexp = /t(e)(st(\d?))/g;
const str = 'test1test2';
const array = [...str.matchAll(regexp)];
console.log(array[0]);
// expected output: Array ["test1", "e", "st1", "1"]
console.log(array[1]);
// expected output: Array ["test2", "e", "st2", "2"]
String.prototype.replace
replace() 用来替换字符串中某个子串,它会返回一个新的字符串。
其第一个参数可以是字符串或者正则表达式,第二个参数可以是字符串和函数。
-
语法
str.replace(regexp|substr, newSubStr|function)
-
参数
regexp (pattern) 一个 RegExp 对象或者其字面量。该正则所匹配的内容会被第二个参数的返回值替换掉。
substr (pattern) 一个将被 newSubStr 替换的 字符串。其被视为一整个字符串,而不是一个正则表达式。仅第一个匹配项会被替换。
newSubStr (replacement) 用于替换掉第一个参数在原字符串中的匹配部分的字符串。该字符串中可以内插一些特殊的变量名。参考下面的使用字符串作为参数。
function (replacement) 一个用来创建新子字符串的函数,该函数的返回值将替换掉第一个参数匹配到的结果。参考下面的指定一个函数作为参数。
-
返回值
一个部分或全部匹配由替代模式所取代的新的字符串。
const mounth1 = 'Jan;Feb;Mar;Apr;May;Jun;Jul;Aug;Sep;Oct;Nov;Dec';
console.log(mounth1.replace(';', ','));
// Jan,Feb;Mar;Apr;May;Jun;Jul;Aug;Sep;Oct;Nov;Dec
console.log(mounth1.replace(';', (res) => res + ','));
// Jan;,Feb;Mar;Apr;May;Jun;Jul;Aug;Sep;Oct;Nov;Dec
const month = 'Jan<Feb,Mar>Apr,May<Jun>Jul,Aug|Sep[Oct]Nov,Dec';
console.log(month.replace(/[<,>|[\]]/g, ';'));
// Jan;Feb;Mar;Apr;May;Jun;Jul;Aug;Sep;Oct;Nov;Dec
console.log(month.replace(/[<,>|[\]]/g, (res) => `{{${res}}}`));
// Jan{{<}}Feb{{,}}Mar{{>}}Apr{{,}}May{{<}}Jun{{>}}Jul{{,}}Aug{{|}}Sep{{[}}Oct{{]}}Nov{{,}}Dec
高级用法
当replace第一个参数是正则表达式,第二个参数是字符串时,还允许在替换字符串中引用匹配到的所有或部分内容。
$$替换为一个 "$"$&替换为匹配的子串$` 替换为当前匹配的子串左边的内容$'替换为当前匹配的子串右边的内容。$n假如第一个参数是 RegExp 对象,并且 n 是个小于 100 的非负整数,那么插入第 n 个括号匹配的字符串。提示:索引是从 1 开始。如果不存在第 n 个分组,那么将会把匹配到到内容替换为字面量。比如不存在第 3 个分组,就会用“$3”替换匹配到的内容。$<Name>这里 Name 是一个分组名称。如果在正则表达式中并不存在分组(或者没有匹配),这个变量将被处理为空字符串。只有在支持命名分组捕获的浏览器中才能使用。
var re = /(\w+)\s(\w+)/;
var str = 'John Smith';
var newstr = str.replace(re, '$2, $1');
// Smith, John
console.log(newstr);
String.prototype.search
search() 方法执行正则表达式和 String 对象之间的一个搜索匹配, 如果匹配成功,则 search() 返回正则表达式在字符串中首次匹配项的索引;否则,返回 -1。
const str = 'hey JudE';
const re = /[A-Z]/g;
const re2 = /[.]/g;
console.log(str.search(re)); // 4
console.log(str.search(re2)); // -1
String.prototype.split
split() 方法使用指定的分隔符字符串将一个 String 对象分割成子字符串数组,以一个指定的分割字串或正则表达式来决定每个拆分的位置。
const month = 'Jan<Feb,Mar>Apr,May<Jun>Jul,Aug|Sep[Oct]Nov,Dec';
console.log(line.split(/\d+/)); // [ 'Oh', 'brave', 'new', 'world.' ]
console.log(month.split(/[<,>|[\]]/));
// [ 'Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep', 'Oct','Nov','Dec' ]
总结
至此,本文已完!本文更多的是一种学习笔记,笔者曾多次学习正则,但又多次忘记,这次决定将整个学习过程完整记录下来,以求掌握正则这一实用工具。本文尽可能完整地介绍了正则的各个部分,但还有一些内容没有介绍,有需要的同学可以去MDN 查看。