正则表达式

121 阅读5分钟

String方法

String.prototype.replace()
  • replace(pattern, replacement)
    • pattern 可以是一个字符串;可以是一个有Symbol.replace的对象,特例是正则表达式。
    • pattern 是字符串or正则(不适用g)的时候,replace只执行一次,要全局替换的话,使用g flag正则,or 使用replaceAll()方法。
  • replacement 是字符串的情况
    console.log("xxx".replace("", "_"));    // "_xxx" 空字符串,匹配最前位置。
    console.log("foo".replace("f", "$$"));  // 替换成$
    console.log("foo".replace("f", "$&oo"));  //结果是foooo,$&替换成匹配的字符串
    console.log("oofoo".replace("f", "$`"));  //结果是oooooo,$`, f替换成匹配字符串以前的部分。
    console.log("oofoo".replace("f", "$'"));  //结果是oooooo,$`,f替换成匹配字符串以后的部分。
    
    // pattern是正则的情况 $n、$<name>对应项的匹配组,命名匹配组,$n中的n最大到100
    // 1.如果pattern是字符串or 正则中相应的匹配组不存在,自己用字符串字面量替换
    "foo".replace(/(f)/, "$2");               // $2匹配组不存在,$2oo
    "foo".replace("f", "$1");                 // $1oo

    // 2.捕获组存在,但是没有匹配上,使用空字符串。
    "foo".replace(/(f)|(g)/, "$2");
    // "oo"; the second group exists but isn't matched
  • replacement是一个函数,每一个匹配完成后,调用这个函数一次
// match==$&, p1==$1,p1==$1,....,offset match在string中的开始位置,string原始字符串
// groups={groupName1:matchedPortions} 如果groupName1没有匹配上,matchedPortions是undefined
// groups仅仅当named capturing group存在时,存在。
function replacer(match, p1, p2, /* …, */ pN, offset, string, groups) {
  return replacement;
}
// 调用两次,第一次:p1 a,p2 undefined
//          第二次:p1 undefined,p2 b
//返回字符串与原字符串相同。
"abc".replace(/(a)|(b)/g, function(match,p1,p2){
    console.log('p1',p1)
    console.log('p2',p2)
    retrun match
})
//Given `styleHyphenFormat('borderTop')`, this returns `'border-top'`.
function styleHyphenFormat(propertyName) {
  function upperToHyphenLower(match, offset, string) {
    return (offset > 0 ? "-" : "") + match.toLowerCase();
  }
  return propertyName.replace(/[A-Z]/g, upperToHyphenLower);
}
// 华式温度转设置温度
function f2c(x) {
  function convert(str, p1, offset, s) {
    return `${((p1 - 32) * 5) / 9}C`;
  }
  const s = String(x);
  const test = /(-?\d+(?:\.\d*)?)F\b/g;
  return s.replace(test, convert);
}
// 给匹配的字符串后添加offset
function addOffset(match, ...args) {
  const hasNamedGroups = typeof args.at(-1) === "object";
  const offset = hasNamedGroups ? args.at(-3) : args.at(-2);
  return `${match} (${offset}) `;
}

console.log("abcd".replace(/(bc)/, addOffset)); // "abc (1) d"
console.log("abcd".replace(/(?<group>bc)/, addOffset)); // "abc (1) d"
String.prototype.replaceAll()
  • replaceAll(pattern, replacement)
  • pattern 如果是正则,必须使用g.没有Symbol.replace方法的pattern都将转化成字符串。

"xxx".replaceAll("", "_"); // "_x_x_x_"

function unsafeRedactName(text, name) {
  return text.replace(new RegExp(name, "g"), "[REDACTED]");
}
function safeRedactName(text, name) {
  return text.replaceAll(name, "[REDACTED]");
}
const report =
  "A hacker called ha.*er used special characters in their name to breach the system.";
console.log(unsafeRedactName(report, "ha.*er")); // "A [REDACTED]s in their name to breach the system."
console.log(safeRedactName(report, "ha.*er")); // "A hacker called [REDACTED] used special characters in their name to breach the system."


String.prototype.match(regexp)
  • 实现是调用参数regexp的Symbol.match 方法, 其实际实现是RegExp.prototype[@@match]().
  • 没有匹配上返回null,
  • paragraph.match()这样调用返回[""],等同于match(/(?:)/)
  • 使用g,返回所有的结果,但是忽略分组的匹配信息;多次调用exec方法,当最后一个匹配失败时,exec自动设置lastIndex to 0。此时match方法没有太大副作用;regexp使用y flag时,匹配失败时并不会自动重置lastIndex,他会返回null.
  • 不使用g,match方法与正则的exec返回的结果是一样地,都已要第一个匹配+分组。
    const str = "For more information, see Chapter 3.4.5.1";
    const re = /see (chapter \d+(\.\d)*)/i;
    const found = str.match(re);

    console.log(found);
    // [
    //   'see Chapter 3.4.5.1',
    //   'Chapter 3.4.5.1',
    //   '.1', 
    //   index: 22,
    //   input: 'For more information, see Chapter 3.4.5.1',
    //   groups: undefined
    // ]
    // `'.1'` was the last value captured by `(.\d)`
    
    
    const re = /[abc]/y;
    for (let i = 0; i < 5; i++) {
      console.log("abc".match(re), re.lastIndex);
    }
    // [ 'a' ] 1
    // [ 'b' ] 2
    // [ 'c' ] 3
    // null 0
    // [ 'a' ] 1
    
    // 匹配-时,失败重置lastIndex。
     const re2 = /[abc]/gy
     console.log("ab-c".match(re2)); // [ 'a', 'b' ]
     console.log(re2.lastIndex)      // 0
     console.log("aa-a".replace(/a/gy, "b")); // "bb-a"
     console.log("bbbaa-a".replace(/a/gy, "b")); // "bbbaa-a"

String.prototype.matchAll()
  • 实现原理,直接调用参数的 Symbol.matchAll 方法,将字符串作为第一个参数传入即可
  • String.prototype.matchAll() 正则参数必须有g;参数不是对象,不是正则,会(例如:字符串)默认转成正则表达式。
  • 不改变regexp.lastIndex的值。
  • Capture groups are ignored when using match() with the global g flag
const regexp = /t(e)(st(\d?))/g;
const str = 'test1test2';
const array = [...str.matchAll(regexp)];
console.log(array[0]);  // Expected output: Array ["test1", "e", "st1", "1"]
console.log(array[1]);  // Expected output: Array ["test2", "e", "st2", "2"]
// 分组被忽略
str.match(regexp); // ['test1', 'test2']
// 传入对象
str.matchAll({
  [Symbol.matchAll](str) {
    return [["Yes, it's interesting."]];
  },
}); // returns [["Yes, it's interesting."]]

正则表达式方法

  • m flag
  • m is used, ^ and $ change from matching at only the start or end of the entire string to the start or end of any line within the string.
/ab+c/i; // 加载完就编译成正则,效率更高。
new RegExp("ab+c", "i"); // constructor 运行时编译成正则,可以动态构造正则
  • regexp.source 返回两条// 中间得字符串。返回的字符串要进行转义,这样两边加上 / 就可以就会形成一个正则表达式。
console.log(new RegExp('/').source);  //\/ 转义就会形成 /// 这是一个注释
console.log(new RegExp('\n').source === '\\n'); // ture  换行符会被转义,应为换行符会破坏正则表达式文本。

  • 正则表达式使用global、sticky flag后(e.g. /foo/g or /foo/y).正则表达式对象是有状态的,维护一个lastIndex属性。
  • sticky就是添加了一个lastIndex,从lastIndex开发搜索字符串。
@@match 可以用来判断是否是正则表达式,
  • 1.x是一个对象。2.x[Symbol.match]不是undefined, 判断是否是truthy,是正则。3、x[Symbol.match]是undefined,判断x是不是由RegExp构造函数创建的。
  • 一个 RegExp 对象,虽然具有 exec 和 @@replace 等其他属性,但其 Symbol.match 属性的值为假值(false),但不是 undefined。在这种情况下,虽然该对象仍然是一个 RegExp 对象,但在使用时会被视为非正则表达式对象。
  • 一个非 RegExp 对象,但具有 Symbol.match 属性的对象,会被视为正则表达式对象。
    const re = /bar/g;
    re[Symbol.match] = false;
    "/bar/g".endsWith(re); // true
    re.exec("bar"); // [ 'bar', index: 0, input: 'bar', groups: undefined ]
    "bar & bar".replace(re, "foo"); // 'foo & foo'
RegExp.prototype.exec()
  • 正则的基础方法,多个方法内部调用这个方法。
    • RegExp.prototype.test 方法内部调用exec方法
  • 不要在while条件中声明正则,会死循环
  • 一定要设置g,不然lastIndex不改变。
  • If the regex may match zero-length characters (e.g. /^/gm), increase its lastIndex manually each time to avoid being stuck in the same place.
  • 使用sticky flag时,global flag将会被忽略。
  • 内部调用exec的方法有
    • 字符串的@@replace、match方法
  • 使用规范
const myRe = /ab*/g;
const str = "abbcdefabh";
let myArray;
while ((myArray = myRe.exec(str)) !== null) {
  let msg = `Found ${myArray[0]}. `;
  msg += `Next match starts at ${myRe.lastIndex}`;
  console.log(msg);
}
// Found abb. Next match starts at 3
// Found ab. Next match starts at 9

1711098157848.png

RegExp.prototype.toString()
  • 返回 /source/flags
  • 在正则表达式中,反斜杠 "" 是一个转义字符,用来表示特殊字符。所以在表示换行符时,可以使用 "\n",也可以使用 "\n",它们是等价的。
console.log(new RegExp('\n', 'g').toString() === new RegExp('\\n', 'g').toString()); // ture

正则表达式的书写

贪婪模式(?、+) vs 非贪婪模式(*?)

    
    const quote = `Single quote "'" and double quote '"'`;
    const regexpQuotes = /(['"]).*?\1/g;
    const regexpQuotesGreedy = /(['"]).*\1/g;
    for (const match of quote.matchAll(regexpQuotes)) {
      console.log(match);// 非贪婪模式输入两次 "'"
      // 贪婪模式:"'" and double quote '"
    }

Groups and backreferences

  • 五种方式:(x)、 (?x)、(?:x)、\n、 \k
  • Capturing group vs Named capturing group
    const personList = `First_Name: John, Last_Name: Doe
    First_Name: Jane, Last_Name: Smith`;

    const regexpNames =
      /First_Name: (?<firstname>\w+), Last_Name: (?<lastname>\w+)/gm;
    /*
    const regexpNames1 = /First_Name: (\w+), Last_Name: (\w+)/gm;
    regexpNames1的结果与regexpNames结果相似,regexpNames1结果groups字段是undefined
    **/
    

    for (const match of personList.matchAll(regexpNames)) {
     /*
    [
        'First_Name: John, Last_Name: Doe','John','Doe',index: 0,
        input: 'First_Name: John, Last_Name: Doe\nFirst_Name: Jane, Last_Name: Smith',
        groups: [Object: null prototype] { firstname: 'John', lastname: 'Doe' }
    ]
     [
      'First_Name: Jane, Last_Name: Smith',
      'Jane',
      'Smith',
      index: 33,
      input: 'First_Name: John, Last_Name: Doe\nFirst_Name: Jane, Last_Name: Smith',
      groups: [Object: null prototype] { firstname: 'Jane', lastname: 'Smith' }
    ]
    */
      console.log(match);
    }

  • Non-capturing group
    // 匹配一个字符串中的日期格式,但只想捕获年份和月份,不想捕获日期和时间
    // 非捕获组(?:-\d{2} \d{2}:\d{2}:\d{2})来匹配日期和时间部分,但不会捕获到这部分内容
    const str = 'Today is 2022-10-01 08:30:00';  
    const regex = /\b(\d{4})-(\d{2})(?:-\d{2} \d{2}:\d{2}:\d{2})\b/; 
  • backreferences

  • \n 引用第n个捕获组匹配的字符串,\k 引用命名捕获组匹配的字符串
    const findDuplicates = 'foo foo bar';
    const regex = /\b(\w+)\s+\1\b/g;
    console.log(findDuplicates.match(regex));
    // Expected output: Array ["foo foo"]
    // `/(?<title>\w+), yes \k<title>/` matches "Sir, yes Sir" in "Do you copy? Sir, yes Sir!"

# Assertions

  • 确定匹配边界的
  • ^, $. 使用m flag,匹配每行中的开头和结尾。
  • \b, \B. /\w\b\w/正则不匹配任何字符串,一个字符不可能连着接\b\w
  • 匹配的是x, x(?=y), x(?!y), (?<=y)x, (?<!y)x
  • /^[^A]/ 匹配非A开头的字符串
  • /x(?!y)/ 与 [^?!]区别
// Using Regex boundaries to fix buggy string.
let buggyMultiline = `tey, ihe light-greon apple
tangs on ihe greon traa`;
buggyMultiline = buggyMultiline.replace(/^t/gim, "h");
console.log(buggyMultiline); // fix 'tey' => 'hey' and 'tangs' => 'hangs' but do not touch 'traa'.

# Character classes

  • [xyz] 、[a-c];要匹配'-',要将'-'放在最后or最前 [abcd-] === [-abcd]
  • [^xyz] [^abcd-] === [^-abcd] [.] 匹配字符'.'
  • x|y /green|red/ matches "green" in "green apple"
  • \ 将普通字符转成特殊字符 /b/--->/\b/; 将特殊字符转成普通字符 /a*/--->/a\*/;要匹配字符 "\" 使用/\\/
if (config.origin === 'top' || config.origin === 'left') {
	distance = /^-/.test(distance) ? distance.substr(1) : `-${distance}`
}

const [value, unit] = distance.match(/(^-?\d+\.?\d?)|(em$|px$|%$)/g)