正则表达式(RegExp)正则总结(js相关)

2,158 阅读5分钟

1. RegExp实例对象基本含义

  • 两个常用属性

    g:global:全局匹配,默认false
    i:ignoreCase:忽略大小写,默认false

  • 元字符

    \大写字母的含义是对\小写字母取反的操作

    ^如果在[]之中,表示除了,即匹配指定范围外的任意单个字符。 ^如果在[]之外,表示以xx开头

    字符等价于含义
    \d[0-9]数字0-9
    \w[a-zA-Z_0-9]数字字母下划线
    \b单词边界
    \s[\t\n\x0B\f\r]空白符,包括空格、制表符、换页符、换行符和其他 Unicode 空格
    \S[^\t\n\x0B\f\r]非空白符
  • 量词和直接量字符

    字符含义
    ?出现零次或一次(可有可无,最多一次), <=1次 等价 {0,1}
    +出现一次或多次(至少出现一次), >0次 等价 {1,},联想记忆:'1加,+1'
    *出现零次或多次(任意次), >=0次 等价 {0,}
    .点号任一字符(换行除外)
    ()分组
    \n换行符(\u000A)
    \t制表符(\u0009)
    \r回车符(\u000D)
  • 选择分组

    这里有两种方法,第一种使用$符号,比如有正则匹配/aa(555)dd(1234)/。那么$1 代表555,$2代表1234$0是所有匹配到的;第二种,使用\,比如\1代表555$选择符是在替换时候的选择,但是如果在正则表达式本身进行替换,就要使用\进行操作了。比如以下的场景
This is is a a dog , I think think this is really a good good dog.
// 如果想要匹配比如is is think think 这样连续的序列,就用到了下面的表达方式: 
(\w+)\s\1

扩展:
.* :  表示任意长度的任意字符,与通配符中的*的意思相同
^$ :表示匹配空行,这里所描述的空行表示"回车",而"空格"或"tab"等都不能算作此处所描述的空行
[]中的特殊字符不需要转义 , 比如/[?]//\?/是一个意思,都代表匹配?字符
/colou?r/ : 既想匹配color又想匹配colour
正则匹配默认是贪婪匹配, 如果想改成懒惰匹配。在现有正则的基础上加一个?即可

  • 多选分支(或), 符号 |
/\.(jpg|png|jpeg|gif)/    //可选jpg结尾的图片格式,也可以是png和其它两种
/(.+)@(163|126|188)\.com$/    //网易邮箱163或126或188都可以

2. 正则常用方法

注意: 正则的方法需要正则调用(reg.test(str),reg.exec(str)),字符串方法需要字符串去调用(str.match(reg),str.search(reg), str.split(reg))。两者不要混淆。

  • 正则的方法

reg.test(str): 测试内容是否符合正则表达式 结果返回true或者false

reg.exec(str): 对字符串进行搜索,并将更新全局RegExp对象的属性以及反映匹配结果,结果跟字符串的macth方法一致

  • 字符串的方法

str.macth(reg): match() 方法会尝试从字符串的起始位置匹配正则表达式,如果匹配,就返回匹配成功的结果,如果不匹配,那就返回null

str.search(reg): search() 扫描整个字符串并返回第一个成功匹配的位置所在下标,没有返回-1

str.split(reg): split是将字符串按照某个字符分隔开,split中不过可以写字符串更可以写正则表达式,这里的正则表达式可以进行非常强大的切割。

  • split的2个注意点:

1、它可以有第二个参数,表示结果数组的最大长度。
2、正则使用分组时,结果数组是包含分隔符的。如果不使用分组,结果数组不包含分隔符

const s = "unicorns,,, and    rainbows And,   Cupcakes";
let str = s.split(/[,\s]+/); //这个正则表示 [1个或多个 逗号或空格]
console.log(str);
// ["unicorns", "and", "rainbows", "And", "Cupcakes"]

let str = "hello,my name is andy凌云. nice to meet you!";
let reg = /([\,\s\.!\?]{1,2})/;
let res = str.split(reg);
//["hello", ",", "my", " ", "name", " ", "is", " ", "andy凌云", ". ", "nice", " ", "to", " ", "meet", " ", "you", "!", ""]
let shortRes = str.split(reg, 3);
//["hello", ",", "my"]
  • str.replace() 基本用法: str.replace(reg,replace|function)。第一个参数是正则,代表匹配内容,第二个参数是替换的字符串或者一个回调函数。重点注意 : 由于第二个参数是回调函数的时候功能非常强大,所以要着重了解一下。是回调函数时,其参数从左到右分别为匹配值,圆括号1匹配值,圆括号2匹配值,...,匹配到第一个时的下标,整个字符串
let s = "study 1314 hard!";
let reg = /(\w+)/g;
let res = s.replace(reg, "$1$1");
res // studystudy 13141314 hardhard!

反向引用

正则中,如果想分组,除了使用相应API可以实现分组,也可以在正则本身里引用分组。 但只能引用之前出现的分组,即反向引用。 语法: \1 , \2 等等,表示与之前出现的第一个括号,第二个括号里的字符一致。

let date1 = '2018-03-06';
let date2 = '2018/03/06';
let date3 = '2018.03.06';
let date4 = '2018.03/06';

let reg = /\d{4}([-\/\.])\d{2}\1\d{2}/;

console.log("reg.test(date1):",reg.test(date1)); //true
console.log("reg.test(date2):",reg.test(date2)); //true
console.log("reg.test(date3):",reg.test(date3)); //true
console.log("reg.test(date4):",reg.test(date4)); //false

3. 一些使用到正则的函数

1. 将敏感词替换成 姓氏"**" 这种模式

实现效果:将刘德华,李湘 替换成刘**, 李*这种格式

let str = "刘德华,李湘,斯琴格日乐是我国优秀的音乐人。不过我觉得斯琴高娃更胜一筹";
let reg = /刘德华|李湘|斯琴格日乐|斯琴高娃/g;
let res = str.replace(reg,(res)=>{
let str = ""
for(let i = 0;i<res.length;i++){
  if(i === 0 ){
    str += res[0];
  }else{
    str+="*"
  }
}
return str;
});
console.log(res);
// 刘**,李*,斯****是我国优秀的音乐人。不过我觉得斯***更胜一筹

2. 将时间字符串转换成年月日时分秒的格式

20191001030520 替换成2019年10月01日03时05分20秒

let str = "20191001030520";
reg = /(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/;
let res = str.replace(reg, ($0, ...arg) => {
console.log($0); //$0为str本身
let addStr = "";
let str = "年月日时分秒";
arg.forEach((item, index) => {
  //因为arg后边还包含匹配的首字母位置和str本身,所以需要做判断然后才能去做拼接
  if (item.length === 4 || item.length === 2) {
    addStr += item + str[index];
  }
});
return addStr;
});
console.log(res); //2019年10月01日03时05分20秒

3. 解析 URL Params 为对象

let url = "http://www.domain.com/?user=andy凌云&id=123&id=456&id=999&city=%E5%B8%9D%E9%83%BD&enabled";
/* 结果
{ 
    user: 'andy凌云',
    id: [ 123, 456, 999 ], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型
    city: '帝都', // 中文需解码
    enabled: true, // 未指定值得 key 约定为 true
}
*/

下面是解析函数

function parseParam(url) {
        const paramsStr = url.split("?")[1]; // 将 ? 后面的字符串取出来
        const paramsArr = paramsStr.split("&"); // 将字符串以 & 分割后存到数组中
        let paramsObj = {};
        // 将 params 存到对象中
        paramsArr.forEach(param => {
          if (/=/.test(param)) {
            // 处理有 value 的参数
            let [key, val] = param.split("="); // 分割 key 和 value
            val = decodeURIComponent(val); // 解码
            val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
            if (paramsObj.hasOwnProperty(key)) {
              // 如果对象有 key,则添加一个值
              paramsObj[key] = [].concat(paramsObj[key], val); //这步注意下
            } else {
              // 如果对象没有这个 key,创建 key 并设置值
              paramsObj[key] = val;
            }
          } else {
            // 处理没有 value 的参数
            paramsObj[param] = true;
          }
        });
        return paramsObj;
      }
parseParam(url);

4. 给数字(正数或小数)加千位分隔符

// 方法①  普通函数
function numFormat(num) {
        num = num.toString().split("."); // 分隔小数点
        let arr = num[0].split("").reverse(); // 转换成字符数组并且倒序排列
        let res = [];
        for (let i = 0, len = arr.length; i < len; i++) {
          if (i % 3 === 0 && i !== 0) {
            res.push(","); // 添加分隔符
          }
          res.push(arr[i]);
        }
        res.reverse(); // 再次倒序成为正确的顺序
        if (num[1]) {
          // 如果有小数的话添加小数部分
          res = res.join("").concat("." + num[1]);
        } else {
          res = res.join("");
        }
        return res;
}

// 方法②  使用正则
function numFormat(num) {
        var res = num.toString().replace(/\d+/, function(n) {
          // 先提取整数部分
          return n.replace(/(\d)(?=(\d{3})+$)/g, function($1) {
            return $1 + ",";
          });
        });
        return res;
}

// 方法③ 使用正则(这种方式更好理解一些)
function numFormat(num) {
        //将数字按 "."分割成2部分。处理"."之前的部分
        let splitStr = num.toString().split(".");
        let newStr = splitStr[0].split("").reverse().join("")
          .replace(/([0-9]{3})/g, "$1,").split("")
          .reverse().join("");
        //如果有小数点之后的部分,就拼接
        if (splitStr[1]) {
          newStr += "." + splitStr[1];
        }
        return newStr;
}

5.单词分割

比如有这么一段代码

let s = "Hello,My name is andy凌云. Nice to Meet you!What's your name?";

如果想实现一个段落的单词分割

res = s.split(/[,.!?;\s]+/);
或 res = s.split(/\W+/);
// "Hello", "My", "name", "is", "andy凌云", "Nice", "to", "Meet", "you", "What's", "your", "name", ""]

如果想将段落的句子分隔开

res = s.split(/[,.!?]+/);
// ["Hello", "My name is andy凌云", " Nice to Meet you", "What's your name", ""]

最后,如果想在分割句子的同时把相应的分隔符保留下来。这是个难点。 注意: 如果想要保留分隔符,只要给匹配的内容分组即可

res = s.split(/([.,!?]+)/);
// ["Hello", ",", "My name is andy凌云", ".", " Nice to Meet you", "!", "What's your name", "?", ""]

注意 : 由于是按照标点符号切割,所以如果最后的字符串是标点符号,则切割成的数字最后一位是空的。可以做下判断来将其删除。

6. 判断一个字符串中出现次数最多的字符,并统计次数

注意 :当有两个或以上字符出现的次数是同样多的时候,也做下处理是更合理的做法

方法① ,正则匹配方法

  let str = "aaababc1111aa1babccccddddddd";
      function getMaxNum(str) {
        //将无规则字符串排列为规则的连续相同字符
        let a = str.split("").sort().join("");
        //将连续相同的字符 切割为 数组
        let res = a.match(/(\w)\1+/g);
        //将数组按照数组长度由长到短排序
        let sortRes = res.sort(function(a, b) {
          return b.length - a.length;
        });
        let obj = {};
        let maxLength = 0;
        // 只取出现次数最多的字符,并按照键(字符)值(长度)写入对象obj中
        sortRes.filter((item, index) => {
          if (index === 0) {
            maxLength = item.length;
          }
          if (item.length < maxLength) {
            return;
          }
          obj[item[0]] = item.length;
        });
        let maxChart = "";
        for (let key in obj) {
          maxChart += key;
        }
        return `出现最多的字符是 ${maxChart} , 最多出现了${maxLength}次`;
      }
      console.log(getMaxNum(str)); //出现最多的字符是 ad , 最多出现了7次

方法② 普通方法

let str = "aaaabbc11111-----+++++?????bbccccddddd"
function getMaxNum(str) {
  let obj = {}
  for (let i = 0; i < str.length; i++) {
    if (obj[str[i]] === undefined) {
      obj[str[i]] = 1
    } else {
      obj[str[i]] += 1
    }
  }
  let maxCount = 0
  let maxStr = []
  let index = 0
  for (let key in obj) {
    if (maxCount === obj[key]) {
      index++
      maxStr[index] = key
    }
    if (maxCount < obj[key]) {
      index = 0
      maxStr.length = 1
      maxStr[index] = key
      maxCount = obj[key]
    }
  }

  return `出现最多的字符是 ${maxStr.join("和")} , 最多出现了${maxCount}次`
}
console.log(getMaxNum(str)) // 出现最多的字符是 1和c和-和+和?和d , 最多出现了5次

7. 将实体字符转换为等值的HTML

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

8. 将字符串转换成正则表达式

使用正则的new RegExp 对象方法 或 eval(不推荐)方法

// 方法1 
let str = 'abc'
let customReg = new RegExp(`^${str}$`)
console.log(customReg.test('abc')); // true
// 方法2 
let str = '/^abc$/'
let customReg = eval(str)
console.log(customReg.test('abc')); // true

9. 按","去分隔字符, 但不包含"()"中的逗号

let reg = /,(?![^\(\)]*?\))/;
let str = 'andy, (1,3),(2,5),(4,7), 凌云';
let res = str.split(reg);
console.log(res);
//["andy", " (1,3)", "(2,5)", "(4,7)", " 凌云"]

10. 匹配前后一致的字符(例如htnl开始和闭合标签)

/**
 * 匹配html中的开始和结束标签, 标签内可以是任意的字符
 * \1代表选择分组。 匹配第一个括号中匹配的字符
 */
replaceHtml() {
  let str =
    "fasfas <html><span>what 312321ever</span></html>321321 <span>whatdsaodsadsja</span>";
  let reg = /<(\w+)>(.+)<\/\1>/g;
  let res = [];
  str.replace(reg, res2=>{
    res.push(res2);
  });
  console.log('%cres','color:transparent;color:red;','!!!!!');
  console.log(res);
  return res
}

4. 常用匹配

  • 去除两头空格 str.replace(/^\s*|\s*$/g,"")

  • 邮箱:^[-\w.]{0,64}@([a-zA-Z0-9]{1,63}\.)*[-a-zA-Z0-9]{1,63}$

  • 身份证正则表达式(15位) : ^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$

  • 身份证正则表达式(18位) : ^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{4}$

  • 手机号 : ^(0|86|17951)?(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$ 或者简单点 ^1[0-9]{10}$

  • 是否小数 : /^\d+\.\d+$/

  • 纯数字 : /^\d{1,}$/

  • html注释 : /^<!--[\s\S]*?-->$/

  • 是否短信验证码

new RegExp(`^\\d{${len}}$`).test(value)
  • 是否html标签(宽松匹配) : /<(.*)>.*<\/\1>|<(.*) \/>/
  • 密码强度正则,最少6位,包括至少1个大写字母,1个小写字母,1个数字,1个特殊字符
/^.*(?=.{6,})(?=.*\d)(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#$%^&*? ]).*$/
  • 用户名正则,4到16位(字母,数字,下划线,减号)
/^[a-zA-Z0-9_-]{4,16}$/
  • 中国手机号(严谨), 根据工信部2019年最新公布的手机号段:
/^(?:(?:\+|00)86)?1(?:(?:3[\d])|(?:4[5-7|9])|(?:5[0-3|5-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[1|8|9]))\d{8}$/
  • 邮箱地址 (email) :
/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/

5. 正则表达式中?=和?:和?!的理解

要理解?=和?!,首先需要理解前瞻,后顾,负前瞻,负后顾四个概念:

// 前瞻:
exp1(?=exp2) 查找exp2前面的exp1
// 后顾:
(?<=exp2)exp1 查找exp2后面的exp1
// 负前瞻:
exp1(?!exp2) 查找后面不是exp2的exp1
// 负后顾:
(?<!exp2)exp1 查找前面不是exp2的exp1

举例:
"中国人".replace(/(?<=中国)人/, "rr") // 匹配中国人中的人,将其替换为rr,结果为 中国rr
"法国人".replace(/(?<=中国)人/, "rr") // 结果为 法国人,因为人前面不是中国,所以无法匹配到

要理解?:则需要理解捕获分组和非捕获分组的概念:

()表示捕获分组,()会把每个分组里的匹配的值保存起来,使用$n(n是一个数字,表示第n个捕获组的内容)
(?:)表示非捕获分组,和捕获分组唯一的区别在于,非捕获分组匹配的值不会保存起来

举例:
// 数字格式化 1,123,000
"1234567890".replace(/\B(?=(?:\d{3})+(?!\d))/g,",") // 结果:1,234,567,890,匹配的是后面是3*n个数字的非单词边界(\B)

在线正则表达式测试: tool.oschina.net/regex

6. 一些小技巧

  1. vscode正则匹配替换console.log(.*)或者//注释

开启正则匹配使用正则表达式.*console.log(.*)\n\s, 这样不光能把console替换掉,而且console所占用的行也一并替换掉了。 同理可删除注释//和他后面的内容 \s*//.*\n\s

  1. vscode正则替换掉某个文本, 并且删除当前文本所在行.*sortable\n\s。 举例: 这个正则可将所有自成一行的sortbale替换掉。

7. 一些比较好的正则相关的文章

多个正则语句