写代码,怎么都会离不开正则表达式,任何一款编辑器也都支持正则表达式搜索替换,基本的语法
字符或表达式 | 说明 |
\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
谈谈正则:等您坐沙发呢!