正则的扩展

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)]
Last Updated: 5/29/2019, 7:30:46 PM