正则的扩展
RegExp 构造函数
1、参数是字符串,第二个参数表示正则表达式的修饰符。
const regExp = new RegExp('abc', 'i');
// 等价于
const regExp = /abc/i;
2、参数是正则,返回一个原有正则表达式的拷贝
const regExp = new RegExp(/abc/i);
// 等价于
const regExp = /abc/i;
ES5 不允许在这种情况下添加第二个参数。
// ES5 中会报错
const regExp = new RegExp(/abc/, 'i');
// ES6
const reqExp = new RegExp(/abc/ig, 'i');
console.log(reqExp); // /abc/i
字符串的正则方法
- match()
- replace()
- search()
- split()
match()
const reqExp = new RegExp(/abc/ig);
const str = 'testabctestabctestabctestab';
console.log(str.match(reqExp)); // ["abc", "abc", "abc"]
replace()
const reqExp = new RegExp(/abcd/ig);
const str = 'testabctestabctestabctestab';
console.log(str.match(reqExp)); // null
const reqExp = new RegExp(/abc/ig);
const str = 'testabctestabctestabctestab';
console.log(str.replace(reqExp, '_')); // test_test_test_testab
search()
const reqExp = new RegExp(/abc/ig);
const str = 'testabctestabctestabctestab';
console.log(str.search(reqExp)); // 4
const reqExp = new RegExp(/abcd/ig);
const str = 'testabctestabctestabctestab';
console.log(str.search(reqExp)); // -1
split()
const reqExp = new RegExp(/abc/ig);
const str = 'testabctestabctestabctestab';
console.log(str.split(reqExp)); // ["test", "test", "test", "testab"]
u 修饰符
Unicode 模式,处理大于 \uFFFF 的 Unicode 字符。
const str = '\uD83D\uDC2A';
const pattern1 = /^\uD83D/u;
const pattern2 = /^\uD83D/;
console.log(pattern1.test(str)); // false
console.log(pattern2.test(str)); // true
1、点字符
点(.)字符:除了换行符以外的任意字符。
const str = '𠮷';
const pattern1 = /^.$/u;
const pattern2 = /^.$/;
console.log(pattern1.test(str)); // true
console.log(pattern2.test(str)); // false 不加 u 修饰符,会认为是两个字符
console.log(str.match(pattern1)); // ["𠮷", index: 0, input: "𠮷", groups: undefined]
console.log(str.match(pattern2)); // null
2、Unicode 字符表示法
ES6 新增了大括号表示 Unicode 字符,必须加上 u 修饰符才能识别。
const str = String.fromCodePoint(0x61); // a
const str2 = 'u'.repeat(62);
const pattern1 = /\u{61}/u;
const pattern2 = /\u{61}/; // 匹配61个连续的 u
console.log(pattern1.test(str)); // true
console.log(pattern2.test(str)); // false
console.log(pattern2.test(str2)); // true
3、量词
使用 u 修饰符后,所有量词都能正确识别大于 0xFFFF 的字符。
const str = '𠮷𠮷';
const pattern1 = /𠮷{2}/u;
const pattern2 = /𠮷{2}/;
console.log(pattern1.test(str)); // true
console.log(pattern2.test(str)); // false
4、预定义模式
/^\S$/ 匹配任何非空白字符。
const str = '𠮷';
const pattern1 = /^\S$/u;
const pattern2 = /^\S$/;
console.log(pattern1.test(str)); // true
console.log(pattern2.test(str)); // false
console.log(str.match(pattern1)); // ["𠮷", index: 0, input: "𠮷", groups: undefined]
console.log(str.match(pattern2)); // null
const str = '𠮷𠮷 𠮷 𠮷';
const pattern1 = /\S/gu;
console.log(str.match(pattern1)); // ["𠮷", "𠮷", "𠮷", "𠮷"]
const pattern2 = /[\s\S]/gu;
console.log(str.match(pattern2)); // ["𠮷", "𠮷", " ", "𠮷", " ", "𠮷"]
正确返回字符串长度的函数
const strLen = (str) => {
const pattern = /[\s\S]/gu;
const result = str.match(pattern);
return result ? result.length : 0;
}
console.log(strLen('𠮷𠮷 𠮷 𠮷')); // 6
5、i 修饰符
const str1 = String.fromCodePoint(0x4B); // "K"
const str2 = String.fromCodePoint(0x212A); // "K"
const pattern1 = /[a-z]/i;
const pattern2 = /[a-z]/iu;
console.log(pattern1.test(str1)); // true
console.log(pattern1.test(str2)); // false
console.log(pattern2.test(str1)); // true
console.log(pattern2.test(str2)); // true
RegExp.prototype.unicode 属性
检测是否设置了 u 修饰符。
const pattern1 = /[a-z]/;
const pattern2 = /[a-z]/u;
console.log(pattern1.unicode); // false
console.log(pattern2.unicode); // true
y 修饰符
ES6 新增了 y 修饰符(sticky)。
与 g 修饰符类似,y 后一次匹配必须从剩余的第一个位置开始。
const str = 'aaa_aa_a';
const pattern1 = /a+/g;
const pattern2 = /a+/y;
console.log(pattern1.exec(str)); // ["aaa", index: 0, input: "aaa_aa_a", groups: undefined]
console.log(pattern2.exec(str)); // ["aaa", index: 0, input: "aaa_aa_a", groups: undefined]
console.log(pattern1.exec(str)); // ["aa", index: 4, input: "aaa_aa_a", groups: undefined]
console.log(pattern2.exec(str)); // null 从 _ 开始匹配,所以匹配不到
y 修饰符隐含了头部匹配的标志 ^。
const str = 'abc';
const pattern = /b/y;
console.log(pattern.test(str)); // false
用 y 修饰符,分词时,不会有遗漏字符。
const tokenize = (pattern, str) => {
let result = [];
let match;
while(match = pattern.exec(str)) {
result.push(match[1]);
}
return result;
};
const pattern1 = /\s*(\+|[0-9]+)\s*/y;
const pattern2 = /\s*(\+|[0-9]+)\s*/g;
const str = '3 + 4';
console.log(tokenize(pattern1, str)); // ["3", "+", "4"]
console.log(tokenize(pattern2, str)); // ["3", "+", "4"]
const str2 = '3a + 4b';
console.log(tokenize(pattern1, str2)); // ["3"]
console.log(tokenize(pattern2, str2)); // ["3", "+", "4"]
RegExp.prototype.sticky 属性
是否设置了 y 修饰符。
const pattern = /\s/y;
console.log(pattern.sticky); // true
RegExp.prototype.flags 属性
const pattern = /\s/y;
console.log(pattern.source); // \s ES5 中的
console.log(pattern.flags); // y ES6 中的
s 修饰符:dotAll 模式
. 字符可以匹配任意字符,但是除了两个特殊字符:
- 四个字节的 UTF-16 字符。可以用 u 修饰符解决
- 终止符(line terminator character)
终止符:
- U+000A 换行符(\n)
- U+000D 回车符(\r)
- U+2028 行分隔符(line separator)
- U+2029 段分隔符(paragraph separator)
const pattern1 = /a.b/;
const str = 'a\nb';
console.log(pattern1.test(str)); // false
const pattern2 = /a.b/s;
console.log(pattern2.test(str)); // true
// 是否有 s 修饰符
console.log(pattern1.dotAll); // false
console.log(pattern2.dotAll); // true
后行断言
先行断言:匹配 y 之前的 x。
const pattern = /x(?=y)/g;
const str = 'xyxxyxx';
let match;
while(match = pattern.exec(str)) {
console.log(match);
}
// ["x", index: 0, input: "xyxxyxx", groups: undefined]
// ["x", index: 3, input: "xyxxyxx", groups: undefined]
先行否定断言:匹配不在 y 之前的 x。
const pattern = /x(?!y)/g;
const str = 'xyxxyxx';
let match;
while(match = pattern.exec(str)) {
console.log(match);
}
// ["x", index: 2, input: "xyxxyxx", groups: undefined]
// ["x", index: 5, input: "xyxxyxx", groups: undefined]
// ["x", index: 6, input: "xyxxyxx", groups: undefined]
后行断言与先行断言相反。
// 匹配 y 后面的 x
const pattern = /(?<=y)x/;
const str1 = 'xy';
const str2 = 'yx';
console.log(pattern.test(str1)); // false
console.log(pattern.test(str2)); // true
后行否定断言。
// 匹配不在 y 后面的 x
const pattern = /(?<!y)x/g;
const str1 = 'xyxaxy';
const str2 = 'yxxaa';
const printMatch = (pattern, str) => {
let match;
while(match = pattern.exec(str)) {
console.log(match);
}
};
console.log('str1 matches:');
printMatch(pattern, str1);
console.log('str2 matches:');
printMatch(pattern, str2);
// str1 matches:
// demo2.js:615 ["x", index: 0, input: "xyxaxy", groups: undefined]
// demo2.js:615 ["x", index: 4, input: "xyxaxy", groups: undefined]
// demo2.js:621 str2 matches:
// demo2.js:615 ["x", index: 2, input: "yxxaa", groups: undefined]
Unicode 属性类
略...
具名组匹配
// ES5 中取匹配组的方式
const regExp = /(\d{4})-(\d{2})-(\d{2})/;
const matchObj = regExp.exec('1999-12-31');
console.log(matchObj); // (4) ["1999-12-31", "1999", "12", "31", index: 0, input: "1999-12-31", groups: undefined]
const year = matchObj[1];
const month = matchObj[2];
const date = matchObj[3];
这样顺序变了,需要改代码。
ES2018 引入了具名组。
const regExp = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/; // <-- 具名组设置
const matchObj = regExp.exec('1999-12-31');
console.log(matchObj); // ["1999-12-31", "1999", "12", "31", index: 0, input: "1999-12-31", groups: {…}]
// groups: {year: "1999", month: "12", day: "31"}
const { groups: { year, month, day: date } } = matchObj;
console.log(year, month, date); // 1999 12 31
结构赋值和替换
有了具名组,就可以对匹配结果进行解构赋值了。
let { groups: { one, two } } = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar');
console.log(one); // 'foo'
console.log(two); // 'bar'
字符串替换时,可以使用 $<组名>
引用具名组。
const regExp = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const result = '2019-05-27'.replace(regExp, '$<day>/$<month>/$<year>');
console.log(result); // '27/05/2019'
replace 的第二个参数也可以是一个函数。
const regExp = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const result = '2019-05-27'.replace(regExp, (
matched, // 整个匹配结果 2015-01-02
capture1, // 第一个组匹配 2015
capture2, // 第二个组匹配 01
capture3, // 第三个组匹配 02
position, // 匹配开始的位置 0
S, // 原字符串 2015-01-02
groups // 具名组构成的一个对象 {year, month, day}
) => {
const { year, month, day } = groups;
return `${day}/${month}/${year}`;
});
console.log(result); // '27/05/2019'
引用
如果要在正则表达式内部引用某个“具名组匹配”,可以使用\k<组名>
的写法。
const regExp = /^(?<mark>[a-z]+)!\k<mark>$/;
console.log(regExp.test('abc!abc')); // true
console.log(regExp.test('abc!ab')); // false
const regExp = /^(?<mark>[a-z]+)!\1$/; // 也可以通过数字索引1
console.log(regExp.test('abc!abc')); // true
console.log(regExp.test('abc!ab')); // false
// 两种引用可以同时使用
const regExp = /^(?<mark>[a-z]+)!\k<mark>!\1$/;
console.log(regExp.test('abc!abc!abc')); // true
console.log(regExp.test('abc!!abc!ab')); // false
String.prototype.matchAll
如果一个正则表达式在字符串里面有多个匹配,现在一般用 g 或 y修饰符,在循环中逐一取出。
const regExp = /t(e)(st(\d?))/g; // 括号代表分组
const str = 'test1test2test3test4';
let match;
let matches = [];
while(match = regExp.exec(str)) {
matches.push(match);
}
console.log(matches);
// ["test1", "e", "st1", "1", index: 0, input: "test1test2test3test4", groups: undefined]
// ["test2", "e", "st2", "2", index: 5, input: "test1test2test3test4", groups: undefined]
// ["test3", "e", "st3", "3", index: 10, input: "test1test2test3test4", groups: undefined]
// ["test4", "e", "st4", "4", index: 15, input: "test1test2test3test4", groups: undefined]
增加 matchAll 方法,可以一次性取出所有匹配。不过返回的是一个遍历器。
const regExp = /t(e)(st(\d?))/g; // 括号代表分组
const str = 'test1test2test3test4';
const matchAll = str.matchAll(regExp);
for (let match of matchAll) {
console.log(match);
}
// // ["test1", "e", "st1", "1", index: 0, input: "test1test2test3test4", groups: undefined]
// // ["test2", "e", "st2", "2", index: 5, input: "test1test2test3test4", groups: undefined]
// // ["test3", "e", "st3", "3", index: 10, input: "test1test2test3test4", groups: undefined]
// // ["test4", "e", "st4", "4", index: 15, input: "test1test2test3test4", groups: undefined]
// 将遍历器转换成数组
console.log(Array.from(str.matchAll(regExp))); // [Array(4), Array(4), Array(4), Array(4)]
console.log([...str.matchAll(regExp)]); // [Array(4), Array(4), Array(4), Array(4)]