文章目录
1 RegExp构造函数
修饰符:
- i:ignore-不区分大小写
- g:global-全局匹配
- m:multi line-多行匹配
- s:使特殊字符圆点
.
中包含换行符\n
在ES5中,RegExp构造函数的参数有两种情况。
-
参数为字符串,第二个参数表示正则表达式的修饰符(flag)
var regex = new RegExp('xyz', 'i') // 等价于 var regex = /xyz/i;
-
参数是一个正则表达式,这时会返回一个原有正则表达式的拷贝,此时不允许有修饰符
var regex = new RegExp(/xyz/i); // 等价于 var regex = /xyz/i;
ES6则允许重新指定修饰符。flags
为获取正则表达式的修饰符。
new RegExp(/abc/ig, 'i').flags
// "i"
2 字符串的正则方法
- match():String.prototype.match 调用 RegExp.prototype[Symbol.match]
- replace():String.prototype.replace 调用 RegExp.prototype[Symbol.replace]
- search():String.prototype.search 调用 RegExp.prototype[Symbol.search]
- split():String.prototype.split 调用 RegExp.prototype[Symbol.split]
3 u修饰符
ES6对正则表达式添加了u修饰符,含义为“Unicode模式”,用来正确处理大于
\uFFFF
的Unicode字符,也就是说,可以正确处理4个字节的UTF-16编码。
/^\uD83D/u.test('\uD83D\uDC2A'); // false
/^\uD83D/.test('\uD83D\uDC2A'); // true
ES5不支持4字节的UTF-16编码,会将其识别为2个字符,导致第二行代码结果为true。加了u修饰符以后,ES6
就会识别为其为一个字符,所以第一行代码结果为false。
3.1 点字符
点(.)字符在正则表达式中的含义是除换行符以外的任意单个字符。对于码点大于
oxFFFF
的Unicode
字符,点字符不能识别,必须加上u修饰符。
var s = "吉";
/^.$/.test(s); // false
/^.$/u.test(s); // true
3.2 Unicode字符表示法
ES6
新增了使用大括号表示Unicode
字符的表示法,这种表示法在正则表达式中必须加上u修饰符才能识别当中的大括号,否则会被解读为量词。
/\u{61}/.test('a'); // false 正则表达式会认为其匹配61个u
/\u{61}/u.test('a'); // true
/\u{20BB7}/u.test('吉'); // true
3.3 量词
使用u修饰符后,所有的量词都会正确识别码点大于0xFFFF
的Unicode
字符
/a{2}/.test('aa'); // true
/a{2}/u.test('aa'); // true
/吉{2}/.test('吉吉'); // false
/吉{2}/u.test('吉吉'); // true
3.4 预定义模式
\S是预定义模式,匹配所有不是空格的字符。只有加上u修饰符,它才能正确匹配码点大于0xFFFF的Unicode字符。
/^\S$/.test('吉'); // false
/^\S$/u.test('吉'); // true
获取正常返回字符串长度的函数
function codePointLength(text) {
var result = text.match(/[\s\S]/gu);
return result ? result.length : 0;
}
var s = "吉吉";
s.length // 4
codePointLength(s) // 2
3.5 修饰符
\u004B
与\u212A
都是大写的K,不加u修饰符则无法识别非规范的K字符
4 y修饰符
粘连(sticky)修饰符,类似于g修饰符。**g修饰符只要在剩余的位置中存在匹配即可,而y修饰符必须保证匹配的是剩余的第一个位置开始。**实质上y修饰符类似于自带头部匹配标识符。
var s = "aaa_aa_a";
var r1 = /a+/g;
var r2 = /a+/y;
r1.exec(s); // ["aaa"]
r2.exec(s); // ["aaa"]
r1.exec(s); // ['aa']
r2.exec(s); // null
可以使用lastIndex
属性来更好的说明y修饰符,该属性可以指定每次开始搜索的位置。同上操作,指定开始位置后,g只需要后面存在匹配即可;y必须在指定位置匹配。
在split方法中使用y修饰符,原字符串必须以分隔符开头。这也意味着,只要匹配成功,数组的第一个成员肯定是空字符串。
// 没有找到匹配
'x##'.split(/#/y);
// ['x##']
// 找到两个匹配
'##x'.split(/#/y);
// ['', '', 'x']
'#x#'.split(/#/y);
// ['', 'x#']
'##'.split(/#/y);
// ['', '', '']
单独一个y修饰符对match方法之返回第一个匹配,必须与g修饰符联用才能返回所有匹配
const REGEX = /a/gy;
'aaxa'.replace(REGEX, "-"); // '--xa'
y修饰符的一个应用是从字符串中提取token(词元),y修饰符确保了匹配之间不会有漏掉的字符。
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;
}
但是一旦出现了非法字符,g修饰符会忽略非法字符,而y修饰符不会。
5 sticky属性
与y修饰符相匹配,表示是否设置了y修饰符。
6 flags属性
获取正则表达式的修饰符
// ES5的source属性
// 返回正则表达式的正文
/abc/ig.source;
// "abc"
// ES6的flags属性
// 返回正则表达式的修饰符
/abc/ig.flags;
// 'gi'
7 s修饰符:dotAll模式
正则表达式中,点(.)是一个特殊字符,代表任意的单个字符,但是行终止符除外。
- U+000A 换行符(\n)
- U+000A 回车符(\r)
- U+2028 行分隔符(line separator)
- U+2029 段分隔符(paragraph separator)
/foo.bar/.test('foo\nbar'); // false
上面的代码中,因为.
不匹配\n
,所以正则表达式返回false
;
但是,很多时候我们希望能匹配人意单个字符,这时有一种变通的写法。
/foo[^]bar/.test('foo\nbar'); // true
提案:dotAll模式
引入s修饰符,使得.
可以匹配任意单个字符。
/foo.bar/s.test('foo\nbar'); // true
可以用dotAll
获取是否处于该模式。
const re = /foo.bar/s;
// 另一种写法
// const re = new RegExp("foo.bar", "s");
rs.test('foo\nbar') // true
re.dotAll // true
re.flags // 's'
/s修饰符和多行修饰符/m不冲突,一起使用时,"."匹配所有的字符,而^
和$
匹配每一行的行首和行尾。
8 后行断言(提案)
JavaScript
语言的正则表达式只支持先行断言(lookahead)和先行否定断言(negativelookahead),不支持后行断言(lookbehind)和后行否定断言(negative lookbehind)。目前,有一个提案(https://github.com/goyakin/es-regexp-lookbehind)被提出。引入后行断言,其中V8引擎4.9版本已经支持。
先行断言:x只有在y前面才匹配,必须写成/x(?=y)/
的形式。比如,只匹配百分号之前的数字,要写成/\d+(?=%)
。
先行否定断言:x只有不在y前面才匹配,必须写成/x(?!y)
的形式。比如,只匹配不在百分号之前的数字,要写成/\d+(?!%)
.
/\d+(?=%)/.exec('100% of US presidents hava been male'); // ["100"]
/\d+(?!%)/.exec('that‘s all 44 of them'); // ["44"]
后行断言:同先行断言相反,形式为/(?<=y)x/
。
后行否定断言:同先行否定断言相反,形式为/(?<!y)x/
。
9 Unicode属性类(提案)
目前有个提案(github.com/mathiasbynens/es-regexp-unicode-property-escapes)中引用一种新的类的写法:\p{...}
和\P(...)
,允许正则表达式匹配符合Unicode某种属性的所有字符。
\P
是\p
的反向匹配,即匹配不满足条件的字符。必须加u修饰符
const regexGreekSymbol = /\p{Script=Greek}/u;
regexGreekSymbol.test('π'); // true
Unicode属性类要指定属性名和属性值。\p{UnicodePropertyName=UnicodePropertyValue}
对于某些属性,可以只写属性名。\p{UnicodePropertyNmae}
。
- Decimal_Number:匹配所有十进制字符
- Number:罗马数字
- Block=Arrows:匹配所有箭头
- 。。。
10 具名组匹配
10.1 简介
正则表达式中使用圆括号进行组匹配
const RE_DATE = /(\d{4}) - (\d{2}) - (\d{2})/;
上面的代码中,正则表达式中有3组圆括号。使用exec方法可以将这3组匹配结果提取出来。
const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj[1]; // 1999
const year = matchObj[2]; // 12
const year = matchObj[3]; // 31
组匹配的一个问题就是每一组的匹配含义不容易看出来,而且只能用数字序号饮用,要是组的顺序变了,引用的时候就必须修改序号。
**提案:**允许为每一个组匹配指定一个名字,即便于阅读代码,又便于引用。(github.com/tc39/proposal-regexp-named-groups)。
在模式头部添加“问号 + 尖括号 + 组名”。同时,数字序号(matchObj[1])仍然有效
const RE_DATE = /(?<year>\d{4}) - (?<month>\d{2}) - (?<day>\d{2})/;
const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj.groups.year; // 1999
const year = matchObj.groups.month; // 12
const year = matchObj.groups.day; // 31
当具名组没有匹配,那么对应的groups对象属性会是undefined。
10.2 解构赋值和替换
let {groups:{one, two}} = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar');
one // foo
two // bar
字符串替换时,使用$<组名>
引用具名组。
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
'2015-01-02'.replace(re, '$<day>/$<month>/$<year>')
// 02/01/2015
// 同上
let result = '2015-01-02'.replace(re, (
matched, // 整个匹配结果2015-01-02
capture1, // 第一组匹配2015
capture2, // 第二组匹配01
capture3, // 第三组匹配02
position, // 匹配开始的位置0
S, // 原字符串2015-01-05
groups // 具名组构成的一个对象[year, month, day]
) => {
let {day, month, year} = groups;
return `${day}/${month}/${year}`;
})
10.3 引用
如果在正则表达式内部引用某个“具名组匹配”,可以使用\k<组名>
的写法。
const RE_TWICE = /^(?<word>[a-z]+)!\k<word>$/;
RE_TWICE.test('abc!abc'); // true
RE_TWICE.test('abc!ab'); // false
数字引用(\1)依然有效。
const RE_TWICE = /^(?<word>[a-z]+)!\1$/;
RE_TWICE.test('abc!abc'); // true
RE_TWICE.test('abc!ab'); // false
两种引用语法可以同时使用。
const RE_TWICE = /^(?<word>[a-z]+)!\k<word>!\1$/;
RE_TWICE.test('abc!abc!abc'); // true
RE_TWICE.test('abc!abc!ab'); // false