简单易理解的正则表达式记录

173 阅读5分钟

正则表达式笔记

两种模糊匹配

  • 正则表达式之所以强大,是因为能实现模糊匹配。

  • 而模糊匹配,有两个方向上的"模糊”。横向模糊纵向模糊

    • 横向模糊是指,一个正则可匹配的字符串的长度不是固定的,可以是多种情况的;
    var regex = /ab{2,5}c/g;
    var string = "abc abbc abbbc abbbbc abbbbbc abbbbbbc";
    console.log(string.match(regex));
    // => ["abbc", "abbbc", "abbbbc", "abbbbbc"]
    
    • 纵向匹配:一个正则匹配的字符串,具体到某一位字符时,他可以不是某个确定的字符,可以有多种可能,如[abc]
    字符组具体含义
    \d表示[0-9]。表示一位数字。digit(数字)
    \D表示[^0-9]。表示除数字以外的任意字符
    \w表示[0-9a-zA-Z_]。表示数字、大小写字母和下划线
    \W表示[^0-9a-za-z_]非单词字符
    \s表示[ \t\v\n\r\f]。表示空白符,包括空格、水平制表符、垂直制表符、换行符、回车符、换页符
    \S表示[^ \t\v\n\r\f]。非空白符
    .表示[^\n\r\u2028\u2029]。通配符,表示几乎任意字符。换行符、回车符、行分隔符和段分割符除外
  • 可以使用[\d\D][\w\w][\s\S]和[^]中的任何一个表示匹配任意字符;

    贪婪匹配与惰性匹配

    var regex = /\d{2,5}/g;
    var string = "123 1234 12345 123456";
    console.log(string.match(regex));
    // => ["123", "1234", "12345", "12345"]
    

    “ /\d{2,5}/g ”是贪婪的,它会尽可能多的匹配。你能给我 6 个,我就要 5 个。你能给我 3 个,我就要 3 个。反正只要在能力范围内,越多越好

    var regex = /\d{2,5}/g;
    var string = "123 1234 12345 123456";
    console.log(string.match(regex));
    // => ["12", "12", "34", "12", "34", "12", "34", "56"]
    

    其中,“ /\d{2,5}?/ ”表示,虽然 2 到 5 次都行,当 2 个就够的时候,就不再往下尝试了。

    惰性量词贪婪量词
    {m,n}?{m,n}
    {m,}?{m,}
    ???
    +?+
    _?_
    • 注意:分支结构也是惰性的/good|goodbye/去匹配“goodbye”字符串时,结果是[“good”];把正则改成/goodbye|good/就是["goodbye"]

    【??(问题)正则表达式中的“#”表示什么意思】

    例子

    • 匹配 id:要求从<div id="container" class="main"></div>提取出 id="container"; 可能最开始想到的方案如下:
    var regex = /id=".*"/;
    var string = '<div id="container" class="main"></div>';
    console.log(string.match(regex)[0]);
    //=>id="container" class="main"
    

    原因是'.'是通配符,本身就匹配双引号的,而量词*又是贪婪的,当遇到 container 后面的双引号时,是不会停下来,会继续匹配,直到遇到最后一个双引号为止。

    一个解决方案:是使用惰性匹配:

    var regex = /id=".*?"/;
    var string = '<div id="container" class="main"></div>';
    console.log(string.match(regex)[0]);
    //=>id="container"
    

    这个方案存在的问题是,效率比较低;

正则表达式位置匹配

  • 在 Es5 中,有 6 个锚:^,$,\b,\B,(?=p),(?!p);
  • ^(脱字符)匹配开头,在多行匹配中匹配行开头;
  • $(美元符号)匹配结尾,在多行匹配中匹配行结尾;
  • \b是单词边界,具体就是\w(数字 0-9 大小写字母下划线) 与\W 之间的位置,也包括\w^(开头)之间的位置,和\w\$(结尾)之间的位置
  • \B是非单词边界;应理解为(非单词)边界,而不是非(单词边界),'它仍然匹配的是边界';正则中所说的单词指的是\w可以匹配的字符,即数字、大小写以及下划线[0-9a-zA-Z].
  • (?=p)其中 p 是一个子模式,即 p 前面的位置,或者说,该位置后面的字符要匹配 p。比如(?=l),表示“l"字符前面的位置,例如:
var result = "hello".replace(/(?=l)/g, "#");
console.log(result);
// => "he#l#lo"

(?!p)就是(?=p)的反面意思;

var result = "hello".replace(/(?!l)/g, "#");
console.log(result);
// => "#h#ell#o#"
  • 总结:
    • (?=p)(?!p)分别就是正向先行断言和负向先行断言。
    • (?<=p)(?<!p)环视,即看看左边,看看右边;
    • (?=p)(?!p),(?<=p)(?<!p)这 4 个就是匹配“位置”
    • (?=p)就是 p 前面的那个位置

位置的特性

  • 对于位置的理解,我们可以理解成空字符“”。

    “hello” == ""+"h"+""+"e"+""+"l"+""+"l"+""+"o"+"";
    

    也等价于:“hello” ==""+""+"hello";

    • 边界:正则中的位置分为"字符的占位" 和"字符的间隙";字符的占位是显示的位置;以 I'm iron man 为例,肉眼可见的字母 符号 空格都是可以占位的字符,也就是可以用下表获取到的位置。 字符的间隙是隐式的位置,即显示位置之间的位置,比如 I 和'之间的位置,字符串开头和 I 之间的位置等。边界:指的是占位的字符左右的间隙位置
  • \b单词边界:单词边界匹配的就是这样的间隙位置:左边占位的字符或右边占位的字符,至少有一个不是\w

// 只有收尾位置匹配
console.log("0aZ_".replace(/\b/g, ".")); //.0aZ_.

// +不是\w,所以它的左右间隙都可以被匹配
console.log("a+a".replace(/\b/g, ".")); //.a.+.a.

// 空格也不是\w,所以它的左右间隙都可以被匹配
console.log("a a".replace(/\b/g, ".")); //.a. .a.
  • \B非单词 边界:它匹配的也是变价,针对的是与\b相反的非单词(\w),也就是说所有不能被\b匹配的边界
// 只有收尾位置匹配
console.log("0aZ_".replace(/\b/g, ".")); //0.a.Z._

// +不是\w,所以它的左右间隙都可以被匹配
console.log("a+a".replace(/\b/g, ".")); //a+a

// 空格也不是\w,所以它的左右间隙都可以被匹配
console.log("a a".replace(/\b/g, ".")); //a a
  • 不匹配任何东西的正则: /.^/

正则表达式括号的作用

  • 括号的作用:提供了分组,便于引用它;
  • 引用某个分组,会有两种情况:在 JS 里引用它,在正则表达式里引用它。
  • (?:P)非捕获匹配
//将每个单词的首字母转换为大写
function titleize(str) {
  return str.toLowerCase().replace(/(?:^|\s)\w/g, function(c) {
    return c.toUpperCase();
  });
}
//驼峰化
function camelize(str) {
  //正则表达式中的(.)表示通配符
  return str.replace(/[-_\s]+(.)?/g, function(match, c) {
    return c ? c.toUpperCase() : "";
  });
}
//中划线化
function dasherize(str) {
  return str
    .replace(/([A-Z])/g, "-$1")
    .replace(/[-_\s]+/g, "-")
    .toLowerCase();
}
// 将驼峰转换成连字符
function (str) {
    return str.replace(hyphenateRE, '-$1').toLowerCase()
  }
//str.replace(/([A-Z])/g,'-$1')表示在匹配到的大写字母前加上中划线

//将HTML特殊字符转换成等值的实体
function escapeHTML(str) {
  var escapeChars = {
    "<": "It",
    ">": "gt",
    '"': "quot",
    "&": "amp",
    "'": "#39"
  };
  return str.replace(
    new RegExp("[" + Object.keys(escapeChars).join("") + "]", "g"),
    function(match) {
      return "&" + escapeChars[match] + ";";
    }
  );
}
console.log(escapeHTML("<div>Blah blah blah</div>"));
// => "&lt;div&gt;Blah blah blah&lt;/div&gt";

// 实体字符转换为等值的HTML。
function unescapeHTML(str) {
  var htmlEntities = {
    nbsp: " ",
    lt: "<",
    gt: ">",
    quot: '"',
    amp: "&",
    apos: "'"
  };
  return str.replace(/\&([^;]+);/g, function(match, key) {
    if (key in htmlEntities) {
      return htmlEntities[key];
    }
    return match;
  });
}
console.log(unescapeHTML("&lt;div&gt;Blah blah blah&lt;/div&gt;"));
// => "<div>Blah blah blah</div>"

正则表达式回溯法原理

  • 回溯法也称试探法,其基本思想是:从问题的某一状态(初始状态)出发,搜索从这种状态出发所能达到的所有“状态”,当一条路走到“尽头”的时候(不能再前进),再后退一步或若干步,从另外一种可能“状态”出发,继续搜索,直到所有的“路径”(状态)都试探过。这种不断“前进”,不断“回溯”的方法,就称作“回溯法”
  • 出现的情况:
    • 贪婪量词
    • 惰性量词
    • 分支结构
  • JS 的正则引擎是 NFA(非确定型有限自动机)
  • 字符字面量、字符组、量词、锚、分组、选择分支、反向引用。

例子

var utils = {};
"Boolean|Number|String|Function|Array|Data|RegExp|Object|Error"
  .split("|")
  .forEach(function(item) {
    utils["is" + item] = function(obj) {
      return {}.toString.call(obj) == "[object" + item + "]";
    };
  });
console.log(utils.isArray([1, 2, 3]));
// => true

相关 API 注意要点

  • 用于正则操作的方法共有 6 个,字符串实例 4 个,正则实例 2 个;
    • String.search(regexp);
    • String.split([separator[,limit]])separator 可以是一个字符串或正则表达式
    • String.match(regexp)
    • String.replace(regexp|substr,newSubStr|function)
    • RegExp.test(str)
    • RegExp.exec(str) 如果匹配失败,exec() 方法返回 null,并将 lastIndex 重置为 0 。
  • 字符串实例的 4 个方法都支持正则和字符串,但 search 和 match,会把字符串转换为正则的;
  • exec 比 match 更强大;当正则没有 g 时,使用 match 返回的信息比较多,但是有 g 后,就没有关键的信息 index;而 exec 方法就能解决该问题,因为它能接着上一次匹配后继续匹配;
  • 正则实例的两个方法 test,exec,当正则是全局匹配时,每一次匹配完成后,都会修改 lastIndex。
  • replace有两种使用形式,这是由于其第二个参数可以是字符串,也可以是函数;当第二个蚕食是字符串时,如下的字符有特殊的含义:
    属性描述
    1,1,2,…,$99匹配第 1-99 个 分组里捕获的文本
    $&匹配到的子串文本
    $`匹配到的子串的左边文本
    $'匹配到的子串的右边文本
    $$美元符号

正则构造函数属性

构造函数的静态属性基于所执行的最近一次正则操作而变化

静态属性描述简写形式
RegExp.input最近一次目标字符串 RegExp["$_"]
RegExp.lastMatch最近一次匹配的文本 RegExp["$&"]
RegExp.lastParen最近一次捕获的文本 RegExp["$+"]
RegExp.leftContext目标字符串中 lastMatch 之前的文本 RegExp["$`"]
RegExp.rightContext目标字符串中 lastMatch 之后的文本 RegExp["$'"]

例子:正则表达式的理解/(?:^|/).?.$/[该正则表达式匹配的是“..”或“/..”]

其中:

  • \表示转义字符;
  • .:匹配的是任意字符;
  • \.:表示匹配的是.;
  • ?:表示匹配0个或1个;
  • $:表示结尾;
  • ?::表示非匹配分组;
  • ():表示用来分组;
  • ^:在这里表示空格;
  • /^|\//:表示匹配一个/
  • |:表示或

总结

  • 非常幸运能看到这篇讲解正则表达式的迷你书,现在能看懂正则表达式了,开心