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("<div>andy凌云</div>"))
// <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. 一些小技巧
- vscode正则匹配替换
console.log(.*)或者//注释
开启正则匹配使用正则表达式.*console.log(.*)\n\s, 这样不光能把console替换掉,而且console所占用的行也一并替换掉了。
同理可删除注释//和他后面的内容 \s*//.*\n\s。
- vscode正则替换掉某个文本, 并且删除当前文本所在行
.*sortable\n\s。 举例: 这个正则可将所有自成一行的sortbale替换掉。