正则表达式自己是一种匹配模式,用计算机语言来描述咱们须要匹配到的结构javascript
正则表达式是一门强大的技术,尤为在处理文本上很有卓效,本文是在阅读诸多资料后,写给本身的正则入门资料,主要参考的老姚的《正则迷你书》,很是感谢大佬的无私奉献,十分推荐去看原书,本人只是拾人牙慧,另外注入了本身对正则的一些理解,适合入门看css
正则表达式从匹配形式来讲,要么匹配字符,要么匹配位置,如下从分别这两点展开学习html
正则匹配到的字符串是不固定的 可使用量词来指定片断出现的次数,次数会影响到字符串的长度,由于称之为横向模糊匹配java
示例正则表达式
var regex = /ab{2,5}c/g ; //g 含有一个a,2-5个b,一个c的字符串
var string = "abc abbc abbbc abbbbc abbbbbc abbbbbbc";
console.log( string.match(regex) );
// 匹配结果,只有知足先后位ac,中间只有2-5个b的字符串都匹配到了
// => ["abbc", "abbbc", "abbbbc", "abbbbbc"]
复制代码
量词编程
指定须要匹配的字符次数,一些常见的次数表示能够用等价的符号代替,以下图:api
表示同一个位置能够匹配多种可能的字符数组
示例dom
var regex = /a[123]b/g; // 匹配以a开头,以b结尾,中间含有123任意其中一个的字符
var string = "a0b a1b a2b a3b a4b";
console.log( string.match(regex) );
// 匹配结果
// => ["a1b", "a2b", "a3b"]
复制代码
字符组ide
常见的纵向模糊匹配集合别名
字符组
1.匹配日期
匹配 2017-06-10
分析
年 四位数字便可 [0-9]{4}
月,分为0开头和1开头 0[1-9] | 1[0-2]
日,分为0、一、二、3开头 0[1-9] | [12][1-9] | 3[01]
正则
var regex = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;
console.log( regex.test("2017-06-10") );
// => true
复制代码
2.匹配id
分析
id="." 可是因为是贪婪匹配,就会匹配到最后一个双引号为止
id=".*?" 可使用惰性匹配来解决,可是效率低下
id="[^"]*" 最佳
正则
var regex = /id="[^"]*"/
var string = '<div id="container" class="main"></div>';
console.log(string.match(regex)[0]);
// => id="container"
复制代码
位置在正则中表示相邻字符之间的位置,有如下描点
表示字符串开头,多行字符表示行开头
表示字符串开头,多行字符表示行结尾
下面咱们能够把字符串的开头和结尾用'#'代替
单行
var result = "hello".replace(/^|$/g, '#');
console.log(result);
// => "#hello#"
复制代码
多行
var result = "I\nlove\njavascript".replace(/^|$/gm, '#');
console.log(result);
/* #I# #love# #javascript# */
复制代码
\b 是单词边界,具体就是 \w 与 \W 之间的位置,也包括 \w 与 ^ 之间的位置,和 \w 与 $ 之间的位置。
var result = "[JS] Lesson_01.mp4".replace(/\b/g, '#');
console.log(result);
// => "[#JS#] #Lesson_01#.#mp4#"
复制代码
var result = "[JS] Lesson_01.mp4".replace(/\B/g, '#');
console.log(result);
// => "#[J#S]# L#e#s#s#o#n#_#0#1.m#p#4"
复制代码
好比 (?=l),表示 "l" 字符前面的位置,不包括p模式匹配的字符
var result = "hello".replace(/(?=l)/g, '#');
console.log(result);
// => "he#l#lo"
复制代码
除匹配p模式前面之外的位置
var result = "hello".replace(/(?!l)/g, '#');
console.log(result);
// => "#h#ell#o#"
复制代码
位置前面要知足匹配p模式,不包括p模式匹配的字符
var result = "hello".replace(/(?<=l)/g, '#');
console.log(result);
// => "hel#l#o"
复制代码
位置前面要知足匹配p模式,除此位置的其他位置
var result = "hello".replace(/(?<=l)/g, '#');
console.log(result);
// => "#h#e#llo#"
复制代码
1.数字千位分隔符表示法
好比把 "12345678",变成 "12,345,678"。
分析
这个匹配一看就是匹配3位数字的前面的位置,可使用先行断言来匹配 ?=\d{3}+, 就能够作到
正则
var result = "12345678".replace(/(?=(\d{3})+$)/g, ',')
console.log(result);
// => "12,345,678"
// 这里尝试3的倍数位数字会发现开头也加上了,
var result = "112345678".replace(/(?=(\d{3})+$)/g, ',')
console.log(result);
// => ",112,345,678"
// 限制此位置不能是开头便可
var regex = /(?!^)(?=(\d{3})+$)/g;
result = "123456789".replace(regex, ',');
console.log(result);
// => "123,456,789"
复制代码
1.实现字符串trim方法
分析
trim是用来去除字符串首尾空白符,嗯,一读这句话,首尾,那不就是首尾 ^ $嘛,至于空白符\s就完事了
正则
function trim(str) {
return str.replace(/^\s+|\s+$/g, '')
}
console.log(trim(' foobar '))
复制代码
trim方法是实现首尾去除空白符的,那么新推出的trimStart,trimEnd如何实现呢,嘻嘻,你们能够想一想
转义这个问题是指一些符号在正则中拥有特殊的含义,好比^表示字符串起始位置,那么如何表示^这个字符串呢,那么就须要特殊的方法,成为转义,转义只须要在字符前加上\
^、$、.、*、+、?、|、\、/、(、)、[、]、{、}、=、!、:、- ,
复制代码
这里的匹配行为由将咱们正则转为计算机语言的状态机决定,常见的有DFA和NFA, 目前比较常见的是NFA,JavaScript也是采用NFA实现的正则引擎,NFA一个特色就是会产生回溯行为,生动地来将,它采用相似深度优先搜索思想,遍历可能匹配的字符串,一旦下一次匹配失效,便可回退到前一个状态,听起来很像拿着线球走出迷宫的故事,回溯行为从直观想就能得知会影响效率,在JavaScript正则中,常见的回溯形式为贪婪量词、惰性量词、分支结构,下面会依次介绍
最大范围匹配
var regex = /\d{2,5}/g;
var string = "123 1234 12345 123456";
console.log( string.match(regex) );
// => ["123", "1234", "12345", "12345"]
复制代码
示例中会将数字尽量匹配到,因此看到匹配的数字都是依照空白符分割开来的
最小匹配范围
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"]
复制代码
示例中,加了?的量词以后,正则会在匹配2个数字以后中止
子模式任选其一 属于惰性匹配具体形式以下:(p1|p2|p3),其中 p一、p2 和 p3 是子模式,用 |(管道符)分隔,表示其中任何之一。
var regex = /good|nice/g;
var string = "good idea, nice try.";
console.log( string.match(regex) );
// => ["good", "nice"]
复制代码
在不少语言语法中,括号最多见的是表明优先级,咱们看看括号在正则表达式中有什么特殊的用途呢?
/ab+/ => a接上至少一个b
如何把量词做用与一个总体
/(ab)+/
复制代码
表达分支的可能性
表示p1或p2表达式任选其一
var regex = /^I love (JavaScript|Regular Expression)$/;
console.log( regex.test("I love JavaScript") );
console.log( regex.test("I love Regular Expression") );
复制代码
用于捕获括号匹配的结果
1.提取数据
括号里的匹配字符串能够被直接引用,用以特定的场景
提取年月日
var regex = /(\d{4})-(\d{2})-(\d{2})/g;
var string = "2017-06-12";
console.log( string.match(regex) );
console.log( RegExp.$1 ); // 2017
console.log( RegExp.$2 ); // 06
console.log( RegExp.$3 ); // 12
复制代码
2.替换
日期更换格式
var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
var result = string.replace(regex, "$2/$3/$1");
console.log(result);
// => "06/12/2017"
复制代码
能够引用以前出现的分组结果
有时候须要引用前面匹配的结果,好比说下面要求日期分隔符一致
// 1表示出现的一个分组中的匹配结果
var regex = /\d{4}(-|\/|\.)\d{2}\1\d{2}/;
var string1 = "2017-06-12";
var string2 = "2017/06/12";
var string3 = "2017.06.12";
var string4 = "2016-06/12";
console.log( regex.test(string1) ); // true
console.log( regex.test(string2) ); // true
console.log( regex.test(string3) ); // true
console.log( regex.test(string4) ); // false
复制代码
不捕获匹配的结果
var regex = /(?:ab)+/g;
var string = "ababa abbb ababab";
console.log( string.match(regex) );
// => ["abab", "ab", "ababab"]
复制代码
// 驼峰化
function camelize (str) {
return str.replace(/[-_\s]+(.)?/g, function (match, c) {
return c ? c.toUpperCase() : '';
});
}
console.log( camelize('-moz-transform') );
// 中划线化
function dasherize (str) {
return str.replace(/([A-Z])/g, '-$1').replace(/[-_\s]+/g, '-').toLowerCase();
}
console.log( dasherize('MozTransform') );
复制代码
emmmm,虽然有优先级,可是有时候仍是会看不懂,使用可视化工具帮助咱们理解是一个很不错的idea,这里有一个正则可视化网址,在阅读不懂的正则表达式能够助你一臂之力。
修饰符 | 描述 |
---|---|
g | 全局匹配,即找到全部的匹配 |
y | 全局匹配,即找到全部的匹配,可是此匹配要求每一个子串是连续下标的 |
i | 忽略字母大小写 |
m | 多行匹配,使用^和$匹配多行时使用 |
默认是找到第一个匹配字符就中止,加了g修饰符就会找到字符串中全部匹配的字符,下面的例子一目了然
var regex = /\d/;
var string = "123";
console.log( string.match(regex) ); // [ '1', index: 0, input: '123' ]
var regex = /\d/g;
var string = "123";
console.log( string.match(regex) ); // [ '1', '2', '3' ]
复制代码
与g行为一致的是,找到全局匹配的子串,可是y有特殊行为,要求每个匹配的子串起始位置必须是上一个子串的结束位置,看一下下面的例子
var s = 'aaa_aa_a';
var r1 = /a+/g;
var r2 = /a+/y;
r1.exec(s) // ["aaa"]
r2.exec(s) // ["aaa"]
r1.exec(s) // ["aa"]
r2.exec(s) // null
复制代码
i的含义比较简单
var regex = /\A/i;
var string = "a";
console.log( string.match(regex) ); // [ 'a', index: 0, input: 'a' ]
var regex = /\A/i;
var string = "A";
console.log( string.match(regex) ); // [ 'A', index: 0, input: 'A' ]
复制代码
m就是为了让^和$变成行头和行尾
var regex = /^A$/g;
var string = "A\nA";
console.log( string.match(regex) ); // null
var regex = /^A$/mg;
var string = "A\nA";
console.log( string.match(regex) ); // [ 'A', 'A' ]
复制代码
在咱们使用正则表达式匹配以后,JavaScript提供了一些操做给咱们使用,下面依次介绍
exec是正则表达式编程中最基本的API,它拥有对字符串匹配,迭代的能力,后续API能够理解为特定场景的exec封装的API,在默认状况下返回第一次匹配的字符串,g修饰符下,每次从上一个匹配结果末端下标开始查找下一个匹配结果
/** 方法返回正则匹配的字符串 * @param string 执行的字符串 * @return {RegExpExecArray | null} 正则执行数组或者null */
exec(string: string): RegExpExecArray | null;
复制代码
let reg = /\d/g;
let s = "123456"
console.log(reg.exec(s)); // [ '1', index: 0, input: '123456' ]
console.log(reg.exec(s)); // [ '2', index: 1, input: '123456' ]
复制代码
检测目标字符串是否有知足匹配的子串,注意验证是有没有子串,比较经常使用的是test,它直接返回boolean表示验证结果
/** 方法返回一个布尔值表示是否在给定字符串中,存在一个匹配正则的字符串 * @param string 被测试的字符串 * @return {boolean} 是否匹配存在匹配子串 */
test(string: string): boolean;
复制代码
能够理解成一下代码
// 如有一个或多个匹配,第一次匹配都能匹配到结果,不然返回null
RegExp.prototype.test = function (str) {
return !!this.exec(str)
}
let reg = /\d/;
let s = "123456"
console.log(reg.test(s)); // true
复制代码
实例
var regex = /\d/;
var string = "abc123";
console.log( regex.test(string) ); // true
复制代码
在匹配标志符位置进行切分
/** 方法按照给定的正则返回分割后的子串数组 * @param separator 分割器,可使一个字符串或者一个正则表达式 * @param limit 返回结果数组的长度 * @return {string []} 分割后的字符串数组 */
split(separator: string | RegExp, limit?: number): string[];
复制代码
能够理解成如下代码
String.prototype.split = function(reg, limit) {
let curIndex = -1
// 此时this为String对象,须要拆包
let str = this.valueOf()
let splitArr = []
// 执行exec方法
while(result = reg.exec(str)) {
let findIndex = result.index
// 分隔符相邻
const isadjoin = curIndex + 1 === findIndex
// 分隔符之间的字符
let splitMiddleStr = str.substring(curIndex + 1, findIndex)
// 这次分割出来的字符
let splitedStr = isadjoin ? "" : splitMiddleStr
splitArr.push(splitedStr)
curIndex = findIndex
}
return splitArr.slice(limit)
}
复制代码
实例
var regex = /,/;
var string = "html,css,javascript";
console.log( string.split(regex) );
// => ["html", "css", "javascript"]
var regex = /,/;
var string = "html,css,javascript";
console.log( string.split(regex, 1) );
// => ["html"]
复制代码
注意点
1.使用正则切分,若正则含有捕获括号, 结果会带有正则匹配部分
var string = "html,css,javascript";
console.log( string.split(/(,)/) );
// =>["html", ",", "css", ",", "javascript"]
复制代码
匹配后提取部分数据,比较经常使用的是match
/** * 方法会以给定的正则去匹配字符串,若匹配成功,则返回匹配数组,不然返回null * @param regexp 字符串或者正则对象 * @return {RegExpMatchArray | null} 匹配结果数组或者null */
match(regexp: string | RegExp): RegExpMatchArray | null;
复制代码
能够理解成如下代码
var string = "2017.06.27";
var regex1 = /\b(\d+)\b/;
var regex2 = /\b(\d+)\b/g;
String.prototype.match = function (reg) {
let str = this.valueOf()
// 是否含有g修饰符
let isGlobal = reg.global
let result = []
let curString = ""
if(isGlobal) {
// 返回所有匹配字符串数组
while(curString = reg.exec(str)) {
result.push(curString[1])
}
}else {
// 返回第一次匹配的字符串数组
result = reg.exec(str)
}
return result
}
console.log(string.match(regex1)); // [ '2017', '2017', index: 0, input: '2017.06.27' ]
console.log(string.match(regex2)); // [ '2017', '06', '27' ]
复制代码
实例
var regex = /^(\d{4})\D(\d{2})\D(\d{2})$/;
var string = "2017-06-26";
console.log( string.match(regex) );
// =>["2017-06-26", "2017", "06", "26", index: 0, input: "2017-06-26"]
复制代码
注意点
1.match会把第一个参数的字符串转成正则
var string = "2017.06.27";
console.log( string.match(".") );
// => ["2", index: 0, input: "2017.06.27"]
//须要修改为下列形式之一
console.log( string.match("\\.") );
console.log( string.match(/\./) );
// => [".", index: 4, input: "2017.06.27"]
// => [".", index: 4, input: "2017.06.27"]
复制代码
2.match返回值
var string = "2017.06.27";
var regex1 = /\b(\d+)\b/;
var regex2 = /\b(\d+)\b/g;
console.log( string.match(regex1) );
// => ["2017", "2017", index: 0, input: "2017.06.27"] 不带g返回第一个匹配的字符串,分组捕获的内容,总体匹配的第一个下标,输入的目标字符串
console.log( string.match(regex2) ); // 带g返回全部匹配的内容
// => ["2017", "06", "27"]
复制代码
替换匹配的信息进行处理,使用replace
/** * 方法按照匹配规则, 使用替换值,将匹配字符串替换为替换值 * @param searchValue 须要匹配的字符串或者正则 * @param replaceValue 替换字符串 * @return {string} 替换后的字符串 */
replace(searchValue: string | RegExp, replaceValue: string): string;
/** * 方法按照匹配规则, * @param searchValue 须要匹配的字符串或者正则 * @param replacer 替换器 * @return {string} 替换后的字符串 */
replace(searchValue: string | RegExp, replacer: (substring: string, ...args: any[]) => string): string;
复制代码
能够理解成如下代码,这里仅实现全局替换,让你们了解replace是一个特定场景下的api便可
var string = "2017.06.27";
var regex2 = /\d+/g;
String.prototype.replace = function (reg, replaceValue) {
// 这里this是String对象,使用valueOf取出字符串值
let str = this.valueOf()
// 先用正则分割字符串
let strArr = str.split(reg)
// 用replaceValue来填充替换的地方
str = strArr.join(replaceValue)
return str
}
console.log(string.replace(regex2, "1")); // 1.1.1
复制代码
实例
var string = "2017-06-26";
var today = new Date( string.replace(/-/g, "/") );
console.log( today );
// => Mon Jun 26 2017 00:00:00 GMT+0800 (中国标准时间)
复制代码
注意点
1.当第二个参数是字符串是,有如下特殊字符
2.当第二个参数是函数时,函数传入参数
// 从左往右分别是匹配的字符串,捕获组,匹配字符串的起始位置,输入字符串
[match, $1, $2, index, input]
let a = "1234 2345 3456".replace(/(\d)\d{2}(\d)/g, function (match, $1, $2, index, input) {
console.log([match, $1, $2, index, input]);
});
// => ["1234", "1", "4", 0, "1234 2345 3456"]
// => ["2345", "2", "5", 5, "1234 2345 3456"]
// => ["3456", "3", "6", 10, "1234 2345 3456"]
复制代码
3.replace能够嵌套使用
在一些需求里,咱们无可避免须要屡次正则处理,好比先找到一个总体,再替换这个总体里面的一部分,以缩小影响范围,能够先用replace匹配一次子串,而后再次缩小范围匹配
let domStr = `<div style="font-family: Times, serif, 'Times New Roman'" class="333"> <div style="font-family: Verdana, Geneva, Tahoma, sans-serif;color: #333"> <div style="font-family: Verdana, Geneva, Tahoma, sans-serif;color: #333"></div> </div> </div>`
// 匹配style="" 关键是中间部分
let styleRegex = /style="[^"]*"/g
let result = domStr.replace(styleRegex, function(style) {
console.log(style);
let isoffFontFamily = /font\-family\:([^;])*(Times New Roman)+([^;"])*/;
return style.replace(isoffFontFamily, "");
})
console.log(result);
// <div style="" class="333">
// <div style="font-family: Verdana, Geneva, Tahoma, sans-serif;color: #333">
// <div style="font-family: Verdana, Geneva, Tahoma, sans-serif;color: #333"></div>
// </div>
// </div>
复制代码
1.通常不使用构造函数生成正则表达式,优先使用字面量,除非须要动态生成正则表达式
2.修饰符都有其对应的对象布尔属性表面是否启用
修饰符 | 实例属性 |
---|---|
g | global |
y | sticky |
i | ingnoreCase |
m | multiline |
3.能够用过source实例属性来查看动态构建的正则表达式结果
var className = "high";
var regex = new RegExp("(^|\\s)" + className + "(\\s|$)");
console.log( regex.source )
// => (^|\s)high(\s|$) 即字符串"(^|\\s)high(\\s|$)"
复制代码
如何针对问题,构建一个合适的正则表达式
1.是否有必要使用正则
人在学习一个新东西以后,很容易陷入到无所不用,正则亦是如此,其实不少时候,咱们可以用字符串API解决,就不须要正则出手,如下是一些例子
var string = "2017-07-01";
var regex = /^(\d{4})-(\d{2})-(\d{2})/;
console.log( string.match(regex) );
// => ["2017-07-01", "2017", "07", "01", index: 0, input: "2017-07-01"]
var result = string.split("-");
console.log( result );
// => ["2017", "07", "01"]
复制代码
在年月日的例子中,咱们使用了正则来获取年月日,相比采用分隔符,增长了代码的复杂性。
2.是否有必要严格匹配
通常来讲,正则因为其复杂性,复杂度上来,须要严格匹配就很困难,应该结合场景,够用就行,或者能够作一些预处理字符串,使得匹配难度下降。
3.效率
1.尽可能使用具体字符组代替通配符,减小回溯
/.*/ 虽然能够匹配任意字符,可是由于贪婪性质,容易出现回溯行为,好比下面的
匹配"abc" 使用/".*"/
// 123"abc"456
复制代码
在匹配过程当中进行了4次回溯,因为回溯须要保存以前的状态,因此须要额外的空间,另外,回溯行为很直观地能够看出来影响效率,/"[^"]*"/,就能够避免回溯行为
2.独立出肯定字符,加快匹配判断速度
/a+/
// 若是能肯定是好比字符a是存在的,能够改写成一下正则,加快匹配判断速度
/aa+/
复制代码
3.对多选分支提取公共部分,减小匹配过程当中可消除的重复
/^abc|^def/ //修改为
/^(?:abc|def)/。
// 多选分支是惰性的,在不匹配分支的时候,会尝试其余分支,公共的部分也会进行这个匹配,然而公共的部分没有必要屡次匹配,能够提取出来,减小匹配中的重复过程
复制代码
4.使用非捕获括号
在咱们不须要捕获括号中的内容时,可使用非捕获括号来省掉本来用户保存捕获内容的内存
1.快速找出Vue源码中的全部正则
尝试一
/\/.*\//
// 这会把注释、路径、域名都匹配进去
// /* */
复制代码
结果
尝试2
/\/(\S)+\//
// 这里匹配了非空字符串,并且必须至少出现一次
// /* */
复制代码
结果
一会儿少了不少,可是仍然会匹配到不是正则的 /ggg/ 可能只是路径中的一部分,可是咱们须要检查的量已经能够接受了。
正则表达式能够说是很是复杂,本文仅仅是本人阅读资料以后对正则自己的知识梳理,至于真正去使用,仍是须要多加练习,面对处理文本状况多,正则就会更多驾轻就熟,本文是在本身面对一个业务须要处理复杂文本状况下,被正则打击,因而不服产生的产物,同时也但愿你们,在了解正则的概貌以后,可以不惧怕正则,能看懂,并写一些简单的,最后本人不是大佬喔,只是有时候死脑筋,想学习多一点,哈哈,因此真要问我正则怎么写,我只能溜了溜了(划掉)。