本文参考了zh.javascript.info/regular-exp… 中的相关内容。
1. 正则表达式创建方式
- 字面量
let regExp = /pattern/modifiers;
//例:
let regExp = /[a-zA-Z]/
- 正则表达式对象
let regExp = new RegExp(parttern,modifiers)
//例:
let regExp = new RegExp('[a-zA-Z]')
pattern(模式) 描述了表达式的模式 modifiers(修饰符) 用于指定全局匹配、不区分大小写的匹配和多行匹配 注意:当使用构造函数创造正则对象时,需要常规的字符转义规则(在前面加反斜杠 \)。比如,以下是等价的:
var re = new RegExp('\\w+')
var re = /\w+/
2. 正则表达式符号
① 修饰符modifiers
- i 表示不区分字符大小写的匹配
(/[a-z]/).test('ASD') //true
- g 执行全局匹配
let reg = /[A-Z]/g
res = 'ASD'.match(reg) //[ 'A', 'D', 'S' ]
- m 执行多行匹配
仅会影响 ^ 和 $ 锚符的行为。在多行模式下,它们不仅仅匹配文本的开始与结束,还匹配每一行的开始与结束。
let str = `1st place: Winnie
2nd place: Piglet
33rd place: Eeyore`;
console.log( str.match(/^\d+/gm) ); // 1, 2, 33
② 锚符
-
^ 匹配文本开头
-
$ 匹配文本结尾
-
^...$ 完全匹配
let goodInput = "12:34";
let badInput = "12:345";
let regexp = /^\d\d:\d\d$/;
console.log( regexp.test(goodInput) ); // true
console.log( regexp.test(badInput) ); // false
锚符^$对比\n
寻找新的一行的话,我们不仅可以使用锚符 ^ 和 $,也可以使用换行符 \n
- 不同点1:换行符会将(\n)加入匹配结果中
- 不同点2:换行符不会匹配字符串结尾
let str = `1st place: Winnie
2nd place: Piglet
33rd place: Eeyore`;
console.log( str.match(/\w+\n/gim) ); // Winnie\n,Piglet\n
③ 括号
(1) []方括号
用于查找某个范围内的字符,表示给定字符中的任意一个,可以从集合或者范围中任意选择 集合:[abc]表示查找在'a''b''c'三个字符中任意一个。尽管在集合中有多个字符,但它们在匹配中只会对应其中的一个。
范围:[a-z]表示匹配a到z范围内的字母
排除范围:[^...] 匹配所有除了给定的字符之外的任意字符
console.log("Java".match(/Java[^script]/))
console.log("JavaScript".match(/Java[^script]/)) //JavaS
[]方括号中的字符不需要转义。通常当我们的确需要查询点字符时,我们需要把它转义成像\. 这样的形式。如果我们需要查询一个反斜杠,我们需要使用\\。除了在方括号中有特殊含义的字符外,其它所有特殊字符都是允许不添加反斜杠的。
// 并不需要转义
let reg = /[-().^+]/g;
alert( "1 + 2 - 3".match(reg) ); // 匹配 +,-
(2) ()圆括号
表示是一个子表达式,正则表达式的一部分用括号括起来(...),也被称为捕获组。有两种作用:
- 将子表达式的匹配项作为单独项存入结果数组。
- 如果将量词{n}放在括号后,子表达式将被视为一个整体。
匹配括号中的内容
括号从左到右编号。正则引擎会记住它们各自匹配的内容,并可以在结果中获得每个子表达式的匹配项。
- 嵌套组 括号可以嵌套。在这种情况下,编号也从左到右。 例如,当需要匹配标签 <span class="my">中:1.整个标签内容span class="my"。2.标签名称:span。3.标签属性:class="my"。
建立正则表达式为:/<(([a-z]+)\s*([^>]*))>/
则每个子表达式的编号方式如图:
let str = '<span class="my">';
let regexp = /<(([a-z]+)\s*([^>]*))>/;
let result = str.match(regexp);
console.log(result[0]); // <span class="my">
console.log(result[1]); // span class="my"
console.log(result[2]); // span
console.log(result[3]); // class="my"
- 可选组 当子表达式未匹配到时,结果数组中也存在对应的结果项,等于undefined。
let match = 'ac'.match(/a(z)?(c)?/)
alert( match.length ); // 3
alert( match[0] ); // ac(完全匹配)
alert( match[1] ); // undefined,因为 (z)? 没匹配项
alert( match[2] ); // c
- 命名组 当子表达式数量较少时,用数字表示是可行的,但对于复杂的正则表达式,可以对子表达式命名。
实现方式:在左括号'('之后添加?<name>进行命名。
例如,查找 “year-month-day” 格式的日期:
let dateRegexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/;
let str = "2019-04-30";
let groups = str.match(dateRegexp).groups;
alert(groups.year); // 2019
alert(groups.month); // 04
alert(groups.day); // 30
- 替换捕获组 str.replace(regexp, replacement)可以使用$n进行匹配替换,n是捕获组/子表达式的编号。
对于子表达式命名的进行替换,使用$<name> 例如,将日期格式从 “year-month-day” 改为 “day.month.year”:
let regexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/g;
let str = "2019-10-30, 2020-01-01";
alert( str.replace(regexp, '$<day>.$<month>.$<year>') );
// 30.10.2019, 01.01.2020
5.非捕获组 有的时候需要添加括号才能正确使用量词进行匹配,但往往子表达式的匹配结果会出现在结果数组中,此时可以在子表达式开头添加?:来取消这个子表达式。
let str = "Gogogo John!";
// ?: 从捕获组中排除 'go'
let regexp = /(?:go)+ (\w+)/i;
let result = str.match(regexp);
alert( result[0] ); // Gogogo John(完全匹配)
alert( result[1] ); // John
alert( result.length ); // 2(数组中没有更多项)
④ 限定符/量词
- *表示匹配0次或多次,相当于{0,}
- +表示匹配1次或多次,相当于{1,}
- ?表示匹配0次或1次,相当于{0,1}
- 数量{n}。在一个字符(或一个字符类等等)后跟着一个量词,用来指出我们具体需要的数量。
- 确切位数,{5}表示5位的数字
- 某个范围的位数,{3,5},表示3-5位的数字
//创建一个正则表达式来查找省略号:连续 3(或更多)个点。
let reg = /\.{3,}/g;
console.log("Hello!... How goes?.....".match(reg));
//创建一个正则表达式来搜寻格式为 #ABCDEF 的 HTML 颜色值:首个字符 # 以及接下来的六位十六进制字符。
let reg = /#[0-9a-fA-F]{6}\b/g
let str = "color:#121212; background-color:#AA00ef bad-colors:f#fddee #fd2 #12345678";
console.log(str.match(reg)) // #121212,#AA00ef
⑤ 元字符
- 数字
- \d (d->digit) 数字:0-9的字符,\d=[0-9]
- \D 非数字:除\d以外的任何字符,\D=[^0-9]
- 空格字符
- \s (s->space) 空格符号:包括空格、制表符\t、换行符\n等,\s=[\n\t\f\v\r]
- \S 匹配任何非空白字符
- 单字符
- \w (w->word) 单个字符:拉丁字母或数字或下划线_,非拉丁字母(如西里尔字母或印地文)不属于 \w。
- \W 匹配除\w以外的任何字符,例如非拉丁字母或空格
- 边缘字符
- \b (b->border) 匹配边缘的字符
- \B 匹配非边缘的字符
⑥ 特殊字符&转义
一个反斜杠 "" 是用来表示匹配字符类的。所以它是一个特殊字符。还存在其它的特殊字符,这些字符在正则表达式中有特殊的含义。它们可以被用来做更加强大的搜索。这里是包含所有特殊字符的列表:
-
[ 中括号表达式开始
-
/ 斜杠符号 '/' 并不是一个特殊符号,但是它被用于在 Javascript 中开启和关闭正则匹配:/...pattern.../,所以我们也应该转义它。
-
\ 将下个字符标记为特殊字符
-
^ 输入字符开始位置,用在[]中表示排除
-
$ 输入字符结束位置
-
. 匹配除换行以外的字符
-
| 两项直接选择一个
-
? 0次或1次
-
* 0次或多次
-
( 子表达式开始
-
) 子表达式结束
如果要把特殊字符作为常规字符来使用,只需要在它前面加个反斜杠。
这种方式也被叫做“转义一个字符”。使用 new RegExp 来创建一个正则表达式实例,需要进行额外的转义。 在字符串中的反斜杠表示转义或者类似 \n 这种只能在字符串中使用的特殊字符。这个引用会“消费”并且解释这些字符,比如说:
- \n —— 变成一个换行字符,
- \u1234 —— 变成包含该码位的 Unicode 字符,
- 其它有些并没有特殊的含义,就像 \d 或者 \z,碰到这种情况的话会把反斜杠移除。 所以调用 new RegExp 会获得一个没有反斜杠的字符串。使用双斜杠,因为引用会把 \ 变为 \
3. 正则表达式RegExp和字符串String的方法
① RegExp中的方法
(1) regexp.exec(str)
regexp.exec(str) 方法返回字符串 str 中的 regexp 匹配项。它是在正则表达式而不是字符串上调用的。 根据正则表达式中是否带修饰符g,返回的结果会有所不同
- 不带g,则regexp.exec(str)返回的匹配与str.match(regexp)完全相同
- 带g,regexp.exec(str)返回第一个匹配项,并将位置信息保存在regexp.lastIndex属性中,下一次调用将从regexp.lastIndex开始搜索并返回匹配项。如果没有匹配项,regexp.exec返回null,并将regexp.lastIndex重置为0。
let str = 'More about JavaScript at https://javascript.info';
let regexp = /javascript/ig;
let result;
while (result = regexp.exec(str)) {
console.log( `Found ${result[0]} at position ${result.index}` );
// Found JavaScript at position 11,然后
// Found javascript at position 33
}
(2) regexp.test(str)
查找匹配项,返回true/false表示是否存在匹配项。
let str = "I love JavaScript";
// 这两个测试相同
console.log( /love/i.test(str) ); // true
console.log( str.search(/love/i) != -1 ); // true
② String中的方法
(1) str.match(refexp)
str.match(regexp) 方法在字符串 str 中找到匹配 regexp 的字符。
- regexp不带g标记,则以数组形式返回第一个匹配项,包含分组(匹配的字符)、属性Index(匹配项位置)、input(输入的字符串,str)。
let str = "I love JavaScript";
let result = str.match(/Java(Script)/);
console.log( result[0] ); // JavaScript(完全匹配)
console.log( result[1] ); // Script(第一个分组)
console.log( result.length ); // 2
// 其他信息:
console.log( result.index ); // 7(匹配位置)
console.log( result.input ); // I love JavaScript(源字符串)
- 带g标记,将所有匹配项作为字符串存入数组,将数组返回为结果。
let str = 'javascript'
let regexp = /([a-c]+)/g
console.log(str.match(regexp)) //[ 'a', 'a', 'c' ]
- 没有匹配项,无论是否带标记g,都返回null而不是空数组
(2) str.matchAll(regexp)
与 match 相比有 3 个区别:
- 它返回包含匹配项的可迭代对象,而不是数组。我们可以用 Array.from 从中得到一个常规数组,也可以使用for...of对这个可迭代对象进行遍历。
- 每个匹配项均以包含分组的数组形式返回(返回格式与不带 g 标记的 str.match 相同)。
- 如果没有结果,则返回的不是 null,而是一个空的可迭代对象。
let str = '<h1>Hello, world!</h1>';
let regexp = /<(.*?)>/g;
let matchAll = str.matchAll(regexp);
console.log(matchAll); // [object RegExp String Iterator],不是数组,而是一个可迭代对象
matchAll = Array.from(matchAll); // 现在返回的是数组
let firstMatch = matchAll[0];
console.log( firstMatch[0] ); // <h1>
console.log( firstMatch[1] ); // h1
console.log( firstMatch.index ); // 0
console.log( firstMatch.input ); // <h1>Hello, world!</h1>
(3) str.split(regexp|substr, limit)
使用正则表达式(或子字符串)作为分隔符来分割字符串
console.log('12, 34, 56'.split(/,\s*/)) // 数组 ['12', '34', '56']
(4) str.search(regexp)
方法str.search(regexp)返回第一个匹配项的位置,如果未找到返回-1。
注意: search 仅查找第一个匹配项。如果需要其他匹配项的位置,则应使用其他方法,例如用 str.matchAll(regexp) 查找所有位置。
(5) str.replace(str|regexp, str|func)
用于搜索和替换的通用方法,replace方法可以不用正则表达式来搜索和替换子字符串。
// 用冒号替换连字符
console.log('12-34-56'.replace("-", ":")) // 12:34-56
当 replace 的第一个参数是字符串时,它仅替换第一个匹配项。如要找到所有的匹配项,应使用带 g 标记的正则表达式 /-/g。
第二个参数可以为以下两种:
- 替代字符串。我们可以在其中使用特殊字符:
| 符号 | 操作解释 |
|---|---|
| $& | 插入整个匹配项 |
| $` | 在匹配项之前插入字符串 |
| $' | 在匹配项之后插入字符串 |
| $n | 如果 n 是一个 1 到 2 位的数字,则将替换项插入第 n 个分组 |
| $<name> | 插入带有给定 name 的括号内 |
| $$ | 插入字符 $ |
- 函数 每次匹配都会调用这个函数,并且返回的值将作为替换字符串插入。 函数 func(match, p1, p2, ..., pn, offset, input, groups) 带参数调用: 如果正则表达式中含有子表达式(带圆括号()),则参数为:
- match - 匹配项,
- p1, p2, ..., pn - 每一个匹配项的分组内容,
- offset - 匹配项的位置,
- input - 源字符串,
- groups - 所指定分组的对象。 如果没有括号,则只有3个参数:func(str, offset, input)。
//所有匹配项大写
let str = "html and css";
let result = str.replace(/html|css/gi, str => str.toUpperCase());
console.log(result); // HTML and CSS
//使用位置替换字符串
console.log("Ho-Ho-ho".replace(/ho/gi, (match, offset) => offset)); // 0-3-6
//交换姓名
let str = "John Smith";
let result = str.replace(/(\w+) (\w+)/, (match, name, surname) => `${surname}, ${name}`);
console.log(result); // Smith, John
如果正则表达式里有多个子表达式,匹配结果包含多个分组,可以用 rest 参数(…)访问。
let str = "John Smith";
let result = str.replace(/(\w+) (\w+)/, (...match) => `${match[2]}, ${match[1]}`);
console.log(result); // Smith, John
如果对子表达式使用?<name>进行了命名,则匹配项返回的结果数组中最后一项一定是包含了命名的groups对象。可以这样获取:
let str = "John Smith";
let result = str.replace(/(?<name>\w+) (?<surname>\w+)/, (...match) => {
let groups = match.pop();
return `${groups.surname}, ${groups.name}`;
});
alert(result); // Smith, John
4.前瞻断言与后瞻断言
1.前瞻断言
语法为:x(?=y),它表示 “匹配 x, 仅在后面是 y 的情况"”
那么对于一个后面跟着 € 的整数金额,它的正则表达式应该为:\d+(?=€)
2.后瞻断言
前瞻断言允许添加一个“后面要跟着什么”的条件判断。 后瞻断言也是类似的,只不过它是在相反的方向上进行条件判断。也就是说,它只允许匹配前面有特定字符串的模式。
语法为:
后瞻肯定断言:(?<=y)x, 匹配 x, 仅在前面是 y 的情况。 后瞻否定断言:(?<!y)x, 匹配 x, 仅在前面不是 y 的情况。
3.用法
| 模式 | 类型 | 匹配 |
|---|---|---|
| x(?=y) | 前瞻肯定断言 | x ,仅当后面跟着 y |
| x(?!y) | 前瞻否定断言 | x ,仅当后面不跟 y |
| (?<=y)x | 后瞻肯定断言 x | ,仅当跟在 y 后面 |
| (?<!y)x | 后瞻否定断言 | x ,仅当不跟在 y 后面 |
-
(?=a) 表示我们需要匹配某样东西的前面。
-
(?!a) 表示我们需要不匹配某样东西。
-
(?:a) 表示我们需要匹配某样东西本身。
-
(?<=a) 表示我们需要匹配某样东西的后面。
-
(?<!a) 表示我们需要不匹配某样东西,与(?!a)方向相反
5. 正则表达式练习
1. 匹配MAC地址
网络接口的MAC地址由6个两位数的十六进制数字组成,并用冒号分隔。
let regexp = /^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$/;
alert( regexp.test('01:32:54:67:89:AB') ); // true
alert( regexp.test('0132546789AB') ); // false (no colons)
alert( regexp.test('01:32:54:67:89') ); // false (5 numbers, must be 6)
alert( regexp.test('01:32:54:67:89:ZZ') ) // false (ZZ ad the end)
2. 匹配颜色
编写匹配格式为#abc或#abcdef的颜色的正则表达式。即:#后面跟着3或6个十六进制数字。
let regexp = /#([0-9a-zA-Z]{3}){1,2}\b/g;
let str = "color: #3f3; background-color: #AA00ef; and: #abcd";
console.log( str.match(regexp) ); // #3f3 #AA00ef
3. 匹配所有数字
匹配所有十进制数,包括整数、浮点数和负数
let regexp = /-?\d+(\.\d+)?/g;
let str = "-0.0 0 2 -023.4.";
console.log( str.match(regexp) ); // -1.5, 0, 2, -123.4
4. 解析表达式
算术表达式由2个数字和一个介于它们之间的运算符组成
function parse(str) {
let reg = /(-?\d+(?:\.\d+)?)\s*([-+*/])\s*(-?\d+(?:\.\d+)?)/
let res = str.match(reg)
if (!res) return []
res.shift()
return res
}
let [a, op, b] = parse("1.2 * 3.4");
console.log(a); // 1.2
console.log(op); // *
console.log(b); // 3.4