JS正则表达式

484 阅读10分钟

本文参考了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) ()圆括号

表示是一个子表达式,正则表达式的一部分用括号括起来(...),也被称为捕获组。有两种作用:

  1. 将子表达式的匹配项作为单独项存入结果数组。
  2. 如果将量词{n}放在括号后,子表达式将被视为一个整体。

匹配括号中的内容

括号从左到右编号。正则引擎会记住它们各自匹配的内容,并可以在结果中获得每个子表达式的匹配项。

  1. 嵌套组 括号可以嵌套。在这种情况下,编号也从左到右。 例如,当需要匹配标签 <span class="my">中:1.整个标签内容span class="my"。2.标签名称:span。3.标签属性:class="my"。

建立正则表达式为:/<(([a-z]+)\s*([^>]*))>/

则每个子表达式的编号方式如图:

image.png

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"
  1. 可选组 当子表达式未匹配到时,结果数组中也存在对应的结果项,等于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
  1. 命名组 当子表达式数量较少时,用数字表示是可行的,但对于复杂的正则表达式,可以对子表达式命名。

实现方式:在左括号'('之后添加?<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
  1. 替换捕获组 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 个区别:

  1. 它返回包含匹配项的可迭代对象,而不是数组。我们可以用 Array.from 从中得到一个常规数组,也可以使用for...of对这个可迭代对象进行遍历。
  2. 每个匹配项均以包含分组的数组形式返回(返回格式与不带 g 标记的 str.match 相同)。
  3. 如果没有结果,则返回的不是 null,而是一个空的可迭代对象。

reg.PNG

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。

第二个参数可以为以下两种:

  1. 替代字符串。我们可以在其中使用特殊字符:
符号操作解释
$&插入整个匹配项
$`在匹配项之前插入字符串
$'在匹配项之后插入字符串
$n如果 n 是一个 1 到 2 位的数字,则将替换项插入第 n 个分组
$<name>插入带有给定 name 的括号内
$$插入字符 $
  1. 函数 每次匹配都会调用这个函数,并且返回的值将作为替换字符串插入。 函数 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 后面
  1. (?=a) 表示我们需要匹配某样东西的前面。

  2. (?!a) 表示我们需要不匹配某样东西。

  3. (?:a) 表示我们需要匹配某样东西本身。

  4. (?<=a) 表示我们需要匹配某样东西的后面。

  5. (?<!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