谈谈正则

写代码,怎么都会离不开正则表达式,任何一款编辑器也都支持正则表达式搜索替换,基本的语法

字符或表达式 说明
\w 匹配单词字符(包括字母、数字、下划线和汉字)
\W 匹配任意的非单词字符(包括字母、数字、下划线和汉字)
\s 匹配任意的空白字符,如空格、制表符、换行符、中文全角空格等
\S 匹配任意的非空白字符
\d 匹配任意数字
\D 匹配任意的非数字字符
[abc] 匹配字符集中的任何字符
[^abc] 匹配除了字符集中包含字符的任意字符
[0-9a-z_A-Z_] 匹配任何数字、字母、下划线。等同于\w
\p{name} 匹配{name}指定的命名字符类中的任何字符
\P{name} 匹配除了{name}指定的命名字符类中之外的任何字符
. 匹配除了换行符号之外的任意字符
[^0-9a-zA-Z_] 等同于\W

常用的转义字符

表达式

可匹配

\r, \n

代表回车和换行符

\t

制表符

\\

代表 "\" 本身

\^

匹配 ^ 符号本身

\$

匹配 $ 符号本身

\.

匹配小数点(.)本身

\W

匹配任意不是字母,数字,下划线,汉字的字符

\S

匹配任意不是空白符的字符

\D

匹配任意非数字的字符

\B

匹配不是单词开头或结束的位置

[^x]

匹配除了x以外的任意字符

[^aeiou]

匹配除了aeiou这几个字母以外的任意字符

限定符

*

匹配前面的子表达式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。 * 等价于{0,}。

+

匹配前面的子表达式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。

?

匹配前面的子表达式零次或一次。例如,"do(es)?" 可以匹配 "do" 或 "does" 中的"do" 。? 等价于 {0,1}。

{n}

n 是一个非负整数。匹配确定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的两个 o。

{n,}

n 是一个非负整数。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等价于 'o+'。'o{0,}' 则等价于 'o*'。

{n,m}

m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。刘, "o{1,3}" 将匹配 "fooooood" 中的前三个 o。'o{0,1}' 等价于 'o?'。请注意在逗号和两个数之间不能有空格。

*?

尽可能少的使用重复的第一个匹配

+?

尽可能少的使用重复但至少使用一次

??

如果有可能使用零次重复或一次重复。

{n}?

等同于{n}

{n,}?

尽可能少的使用重复但至少重复n次

{n,m}?

介于n次和m次之间,尽可能少的使用重复。

js中使用正则表达式:
使用RegExp 构造函数的形式来定义正则时,RegExp可以传递2个参数,第二个参数是可选的,当传递一个参数时,返回一个原有的正则表达式,传递2个参数时,第二个参数表示表达式的修饰符ES5中只能传递一个参数
var reg = new RegExp('/abc/','i') // 等同于 reg = /abc/i;
typeof reg // 'object'
说说u修饰符 含义为“Unicode 模式”,用来正确处理大于\uFFFF的 Unicode 字符,
点字符(.)对于码点大于0xFFFF的 Unicode 字符,点字符不能识别,必须加上u修饰符eg:
var s = '';
/^.$/.test(s) // false
/^.$/u.test(s) // true

ES6 新增了使用大括号表示 Unicode 字符,这种表示法在正则表达式中必须加上u修饰符,才能识别当中的大括号,否则会被解读为量词使用u修饰符,所有量词才能正确识别码点大于0xFFFF的 Unicode 字符eg:
/\u{61}/.test('a') // false
/\u{61}/u.test('a') // true
/\u{20BB7}/u.test('') // true
/a{2}/.test('aa') // true
/a{2}/u.test('aa') // true
/{2}/.test('') // false
/{2}/u.test('') // true

在ES6中某些特殊的字符,在使用正则表达式匹配时还是尽肯能的加上u修饰符,不然遇到某些Unicode码点会识别不了
y修饰符
与g修饰符类似,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始eg:
var s = 'aaa_aa_a';
var r = /a+_/y;

r.exec(s) // ["aaa_"]
r.exec(s) // ["aa_"]
通过index更能直接的显示y修饰符
const REGEX = /a/g;

// 指定从2号位置(y)开始匹配
REGEX.lastIndex = 2;

// 匹配成功
const match = REGEX.exec('xaya');

// 在3号位置匹配成功
match.index // 3

// 下一次匹配从4号位开始
REGEX.lastIndex // 4

// 4号位开始匹配失败
REGEX.exec('xaya') // null
// 下面是y修饰符
const REGEX = /a/y;

// 指定从2号位置开始匹配
REGEX.lastIndex = 2;

// 不是粘连,匹配失败
REGEX.exec('xaya') // null

// 指定从3号位置开始匹配
REGEX.lastIndex = 3;

// 3号位置是粘连,匹配成功
const match = REGEX.exec('xaya');
match.index // 3
REGEX.lastIndex // 4

应用在字符串提取token等eg:

const TOKEN_Y = /\s*(\+|[0-9]+)\s*/y;
const TOKEN_G  = /\s*(\+|[0-9]+)\s*/g;

tokenize(TOKEN_Y, '3 + 4')
// [ '3', '+', '4' ]
tokenize(TOKEN_G, '3 + 4')
// [ '3', '+', '4' ]

function tokenize(TOKEN_REGEX, str) {
  let result = [];
  let match;
  while (match = TOKEN_REGEX.exec(str)) {
    result.push(match[1]);
  }
  return result;
}
tokenize(TOKEN_Y, '3x + 4')
// [ '3' ]
tokenize(TOKEN_G, '3x + 4')
// [ '3', '+', '4' ] g修饰符会忽略非法字符,而y修饰符不会

flags属性返回当前的修饰符eg:
/a/gui.flags // "gui"
source属性返回正则表达式正文eg:
/a/gui.source // "a"
s修饰符
点(.)是一个特殊的字符,正则表达式中表示任意单个字符,有时却不能匹配到换行符(\n)、回车符(\r)、行分隔符(line separator)、 段分隔符(paragraph separator),所以在ES6中加入s修饰符,然后(.)就能匹配到这些了eg:
/foo.bar/.test('foo\nbar')
// false
/foo[^]bar/.test('foo\nbar')
// true 当然可以通过变通来达到匹配
/foo.bar/s.test('foo\nbar') // true ES6中直接使用s修饰符达到效果

之前js正则表达式中只引入了先行断言
“先行断言”指的是,x只有在y前面才匹配,必须写成/x(?=y)/。比如,只匹配百分号之前的数字,要写成/\d+(?=%)/。“先行否定断言”指的是,x只有不在y前面才匹配,必须写成/x(?!y)/。比如,只匹配不在百分号之前的数字,要写成/\d+(?!%)/
ES6中加入后行断言
“后行断言”正好与“先行断言”相反,x只有在y后面才匹配,必须写成/(?<=y)x/。比如,只匹配美元符号之后的数字,要写成/(?<=\$)\d+/。”后行否定断言“则与”先行否定断言“相反,x只有不在y后面才匹配,必须写成/(?<!y)x/。比如,只匹配不在美元符号后面的数字,要写成/(?<!\$)\d+/
“后行断言”的实现,需要先匹配/(?<=y)x/的x,然后再回到左边,匹配y的部分。这种“先右后左”的执行顺序,与所有其他正则操作相反eg:
/(?<=(\d+)(\d+))$/.exec('1053') // ["", "1", "053"]
/^(\d+)(\d+)$/.exec('1053') // ["1053", "105", "3"]

没有“后行断言”时,第一个括号是贪婪模式,第二个括号只能捕获一个字符,所以结果是105和3。而“后行断言”时,由于执行顺序是从右到左,第二个括号是贪婪模式,第一个括号只能捕获一个字符,所以结果是1和053。
Unicode特殊写法
一种新的类的写法\p{...}和\P{...},允许正则表达式匹配符合 Unicode 某种属性的所有字符
语法:\p{属性名=属性值} // 某些也可以直接写属性名或是属性值大写的P是小写的p的反相匹配,在使用这种正则表达式时必须使用u修饰符,不然\p或是\P都会报错eg:
const regex = /^\p{Decimal_Number}+$/u;
regex.test('') // true
// 匹配所有数字
const regex = /^\p{Number}+$/u;
regex.test('²³¹¼½¾') // true
regex.test('㉛㉜㉝') // true
regex.test('ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ') // true
// 匹配所有空格
\p{White_Space}

// 匹配各种文字的所有字母,等同于 Unicode 版的 \w
[\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]

// 匹配各种文字的所有非字母的字符,等同于 Unicode 版的 \W
[^\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]

// 匹配 Emoji
/\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F/gu

// 匹配所有的箭头字符
const regexArrows = /^\p{Block=Arrows}+$/u;
regexArrows.test('←↑→↓↔↕↖↗↘↙⇏⇐⇑⇒⇓⇔⇕⇖⇗⇘⇙⇧⇩') // true

具名组匹配:给正则表达式的各个匹配项起别名eg:
const RE_DATE = /(?\d{4})-(?\d{2})-(?\d{2})/;

const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj.groups.year; // 1999
const month = matchObj.groups.month; // 12
const day = matchObj.groups.day; // 31


“具名组匹配”在圆括号内部,模式的头部添加“问号 + 尖括号 + 组名”(?),然后就可以在exec方法返回结果的groups属性上引用该组名,具名组匹配等于为每一组匹配加上了 ID,便于描述匹配的目的。如果组的顺序变了,也不用改变匹配后的处理代码

若没有匹配成功的,则对应的groups对象的属性值将为undefined但键值还是一直存在groups对象中 eg:
const RE_OPT_A = /^(?a+)?$/;
const matchObj = RE_OPT_A.exec('');

matchObj.groups.as // undefined
'as' in matchObj.groups // true

相关应用:解构赋值
let {groups: {one, two}} = /^(?.*):(?.*)$/u.exec('foo:bar');
one // foo
two // bar

let re = /(?\d{4})-(?\d{2})-(?\d{2})/u;

'2015-01-02'.replace(re, '$/$/$')
// '02/01/2015'
在正则表达式内部引用某个“具名组匹配”,可以使用\k<组名>的写法eg:
const RE_TWICE = /^(?[a-z]+)!\k$/;
RE_TWICE.test('abc!abc') // true
RE_TWICE.test('abc!ab') // false
数字引用(\1)依然有效。
const RE_TWICE = /^(?[a-z]+)!\1$/;
RE_TWICE.test('abc!abc') // true
RE_TWICE.test('abc!ab') // false

百度已收录
分享