转载自https://juejin.im/post/59b5e50f51882519777c4815git
一种几乎能够在全部的程序设计语言里和全部的计算机平台上使用的文字处理工具。它能够用来查找特定的信息(搜索),也能够用来查找并编辑特定的信息(替换)。 核心是 匹配,匹配位置或者匹配字符正则表达式
.
: 匹配除了换行符以外的任何单个字符\
: 在非特殊字符以前的反斜杠表示下一个字符是特殊的,不能从字面上解释。例如,没有前\的'b'一般匹配小写'b',不管它们出如今哪里。若是加了'',这个字符变成了一个特殊意义的字符,反斜杠也能够将其后的特殊字符,转义为字面量。例如,模式 /a*/
表明会匹配 0 个或者多个 a。相反,模式 /a\*/
将 '*' 的特殊性移除,从而能够匹配像 "a*"
这样的字符串。|
: 逻辑或操做符[]
:定义一个字符集合,匹配字符集合中的一个字符,在字符集合里面像 .
,\
这些字符都表示其自己[^]
:对上面一个集合取非-
:定义一个区间,例如[A-Z]
,其首尾字符在 ASCII 字符集里面{m,n}
:匹配前面一个字符至少 m 次至多 n 次重复,还有{m}
表示匹配 m 次,{m,}
表示至少 m 次+
: 匹配前面一个表达式一次或者屡次,至关于 {1,}
,记忆方式追加(+),起码得有一次*
: 匹配前面一个表达式零次或者屡次,至关于 {0,}
,记忆方式乘法(*),能够一次都没有?
: 单独使用匹配前面一个表达式零次或者一次,至关于 {0,1}
,记忆方式,有吗?,有(1)或者没有(1),若是跟在任何量词*
,+
,?
,{}
后面的时候将会使量词变为非贪婪模式(尽可能匹配少的字符),默认是使用贪婪模式。好比对 "123abc" 应用 /\d+/
将会返回 "123",若是使用 /\d+?/
,那么就只会匹配到 "1"。^
: 单独使用匹配表达式的开始\b
:匹配单词边界\B
:匹配非单词边界(?=p)
:匹配 p 前面的位置(?!p)
:匹配不是 p 前面的位置\d
:[0-9]
,表示一位数字,记忆方式 digit\D
:[^0-9]
,表示一位非数字\s
:[\t\v\n\r\f]
,表示空白符,包括空格,水平制表符(\t
),垂直制表符(\v
),换行符(\n
),回车符(\r
),换页符(\f
),记忆方式 space character\S
:[^\t\v\n\r\f]
,表示非空白符\w
:[0-9a-zA-Z]
,表示数字大小写字母和下划线,记忆方式 word\W
:[^0-9a-zA-Z]
,表示非单词字符g
: 全局搜索 记忆方式globali
:不区分大小写 记忆方式 ignorem
:多行搜索search search 接受一个正则做为参数,若是参入的参数不是正则会隐式的使用 new RegExp(obj)将其转换成一个正则,返回匹配到子串的起始位置,匹配不到返回-1数组
match 接受参数和上面的方法一致。返回值是依赖传入的正则是否包含 g ,若是没有 g 标识,那么 match 方法对 string 作一次匹配,若是没有找到任何匹配的文本时,match 会返回 null ,不然,会返回一个数组,数组第 0 个元素包含匹配到的文本,其他元素放的是正则捕获的文本,数组还包含两个对象,index 表示匹配文本在字符串中的位置,input 表示被解析的原始字符串。若是有 g 标识,则返回一个数组,包含每一次的匹配结果bash
var str = 'For more information, see Chapter 3.4.5.1';
var re = /see (chapter \d+(\.\d)*)/i;
var found = str.match(re);
console.log(found);
// (3) ["see Chapter 3.4.5.1", "Chapter 3.4.5.1", ".1", index: 22, input: "For more information, see Chapter 3.4.5.1"]
// 0:"see Chapter 3.4.5.1"
// 1:"Chapter 3.4.5.1"
// 2:".1"
// index:22
// input:"For more information, see Chapter 3.4.5.1"
// length:3
// __proto__:Array(0)
// 'see Chapter 3.4.5.1' 是整个匹配。
// 'Chapter 3.4.5.1' 被'(chapter \d+(\.\d)*)'捕获。
// '.1' 是被'(\.\d)'捕获的最后一个值。
// 'index' 属性(22) 是整个匹配从零开始的索引。
// 'input' 属性是被解析的原始字符串。
复制代码
var str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
var regexp = /[A-E]/gi;
var matches_array = str.match(regexp);
console.log(matches_array);
// ['A', 'B', 'C', 'D', 'E', 'a', 'b', 'c', 'd', 'e']
复制代码
replace 接受两个参数,第一个是要被替换的文本,能够是正则也能够是字符串,若是是字符串的时候不会被转换成正则,而是做为检索的直接量文本。第二个是替换成的文本,能够是字符串或者函数,字符串可使用一些特殊的变量来替代前面捕获到的子串函数
变量名 | 表明的值 |
---|---|
$$ | 插入一个 "$"。 |
$& | 插入匹配的子串。 |
$` | 插入当前匹配的子串左边的内容。 |
$' | 插入当前匹配的子串右边的内容。 |
$n | 假如第一个参数是 RegExp对象,而且 n 是个小于100的非负整数,那么插入第 n 个括号匹配的字符串。 |
```
var re = /(\w+)\s(\w+)/;
var str = "John Smith";
var newstr = str.replace(re, "$2, $1");
// Smith, John
console.log(newstr);
```
复制代码
若是是函数的话,函数入参以下,返回替换成的文本工具
变量名 | 表明的值 |
---|---|
match | 匹配的子串。(对应于上述的$&。) |
p1,p2,... | 假如replace()方法的第一个参数是一个RegExp 对象,则表明第n个括号匹配的字符串。(对应于上述的$,1,$2等。) |
offset | 匹配到的子字符串在原字符串中的偏移量。(好比,若是原字符串是“abcd”,匹配到的子字符串是“bc”,那么这个参数将是1) |
string | 被匹配的原字符串。 |
function replacer(match, p1, p2, p3, offset, string) {
// p1 is nondigits, p2 digits, and p3 non-alphanumerics
return [p1, p2, p3].join(' - ');
}
var newString = 'abc12345#$*%'.replace(/([^\d]*)(\d*)([^\w]*)/, replacer);
// newString abc - 12345 - #$*%
复制代码
split 接受两个参数,返回一个数组。第一个是用来分割字符串的字符或者正则,若是是空字符串则会将元字符串中的每一个字符以数组形式返回,第二个参数可选做为限制分割多少个字符,也是返回的数组的长度限制。有一个地方须要注意,用捕获括号的时候会将匹配结果也包含在返回的数组中post
var myString = "Hello 1 word. Sentence number 2.";
var splits = myString.split(/\d/);
console.log(splits);
// [ "Hello ", " word. Sentence number ", "." ]
splits = myString.split(/(\d)/);
console.log(splits);
// [ "Hello ", "1", " word. Sentence number ", "2", "." ]
复制代码
test 接受一个字符串参数,若是正则表达式与指定的字符串匹配返回 true 不然返回 false网站
execui
一样接受一个字符串为参数,返回一个数组,其中存放匹配的结果。若是未找到匹配,则返回值为 null。url
匹配时,返回值跟 match 方法没有 g 标识时是同样的。数组第 0 个表示与正则相匹配的文本,后面 n 个是对应的 n 个捕获的文本,最后两个是对象 index 和 input
同时它会在正则实例的 lastIndex 属性指定的字符处开始检索字符串 string。当 exec() 找到了与表达式相匹配的文本时,在匹配后,它将把正则实例的 lastIndex 属性设置为匹配文本的最后一个字符的下一个位置。
有没有 g 标识对单词执行 exec 方法是没有影响的,只是有 g 标识的时候能够反复调用 exec() 方法来遍历字符串中的全部匹配文本。当 exec() 再也找不到匹配的文本时,它将返回 null,并把 lastIndex 属性重置为 0。
var string = "2017.06.27";
var regex2 = /\b(\d+)\b/g;
console.log( regex2.exec(string) );
console.log( regex2.lastIndex);
console.log( regex2.exec(string) );
console.log( regex2.lastIndex);
console.log( regex2.exec(string) );
console.log( regex2.lastIndex);
console.log( regex2.exec(string) );
console.log( regex2.lastIndex);
// => ["2017", "2017", index: 0, input: "2017.06.27"]
// => 4
// => ["06", "06", index: 5, input: "2017.06.27"]
// => 7
// => ["27", "27", index: 8, input: "2017.06.27"]
// => 10
// => null
// => 0
复制代码
其中正则实例lastIndex属性,表示下一次匹配开始的位置。
好比第一次匹配了“2017”,开始下标是0,共4个字符,所以此次匹配结束的位置是3,下一次开始匹配的位置是4。
从上述代码看出,在使用exec时,常常须要配合使用while循环:
var string = "2017.06.27";
var regex2 = /\b(\d+)\b/g;
var result;
while ( result = regex2.exec(string) ) {
console.log( result, regex2.lastIndex );
}
// => ["2017", "2017", index: 0, input: "2017.06.27"] 4
// => ["06", "06", index: 5, input: "2017.06.27"] 7
// => ["27", "27", index: 8, input: "2017.06.27"] 10
复制代码
精确匹配就不说了,好比/hello/,也只能匹配字符串中的"hello"这个子串。 正则表达式之因此强大,是由于其能实现模糊匹配。
用{m,n}
来匹配多种数量,其余几种形式(+*?
)均可以等价成这种。好比
var regex = /ab{2,5}c/g;
var string = "abc abbc abbbc abbbbc abbbbbc abbbbbbc";
console.log( string.match(regex) ); // ["abbc", "abbbc", "abbbbc", "abbbbbc"]
复制代码
贪婪和非贪婪:
默认贪婪
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"]
复制代码
用字符组[]
来匹配多种状况,其余几种形式(\d\D\s\S\w\W
)均可以等价成这种。好比
var regex = /a[123]b/g;
var string = "a0b a1b a2b a3b a4b";
console.log( string.match(regex) ); // ["a1b", "a2b", "a3b"]
复制代码
若是字符组里面字符特别多的话能够用-
来表示范围,好比[123456abcdefGHIJKLM],能够写成[1-6a-fG-M],用[^0-9]表示非除了数字之外的字符 多种状况还能够是多种分支,用管道符来链接|
,好比
var regex = /good|goodbye/g;
var string = "goodbye";
console.log( string.match(regex) ); // ["good"]
复制代码
这个例子能够看出分支结构也是惰性的,匹配到了就再也不日后尝试了。
掌握这两种方式就能够解决比较简单的正则问题了。
/^([1-9]\d*|0)(\.\d{1,2})?$/
/(\+86)?1\d{10}/
/^(\d{15}|\d{17}([xX]|\d))$/
位置是相邻字符之间的,好比,有一个字符串 hello
,这个字符串一共有6个位置 *h*e*l*l*o*
, *表明位置
^
,$
匹配字符的开头和结尾,好比 /^hello$/
匹配一个字符串,要符合这样的条件,字符串开头的位置,紧接着是 h 而后是 e,l,l,o 最后是字符串结尾的位置 位置还能够被替换成字符串,好比 'hello'.replace(/^|$/g, '#')
结果是 #hello#
/b
,/B
匹配单词边界和非单词边界,单词边界具体指 \w
([a-zA-Z0-9_]
) 和 \W
之间的位置,包括 \w
和 ^
以及 $
之间的位置,好比 'hello word [js]_reg.exp-01'.replace(/\b/g, '#')
结果是 #hello# #word# [#js#]#_reg#.#exp#-#01#
(?=p)
,(?!p)
匹配 p 前面的位置和不是 p 前面位置,好比 'hello'.replace(/(?=l)/g, '#')
结果是 he#l#lo
'hello'.replace(/(?!l)/g, '#')
结果是 #h#ell#o#
字符与字符之间的位置能够是多个。在理解上能够将位置理解成空字符串 '',好比 hello
能够是通常的 '' + 'h' + 'e' + 'l' + 'l' + 'o' + ''
,也能够是 '' + '' + '' + '' + 'h' + 'e' + 'l' + 'l' + 'o' + ''
, 因此/^h\Be\Bl\Bl\Bo$/.test('hello')
结果是 true,/^^^h\B\B\Be\Bl\Bl\Bo$$$/.test('hello')
结果也是 true
123123213.replace(/(?=(\d{3})+$)/g, ',')
可是这样的话会在最前面也加一个 ',' 这明显是不对的。因此还得继续改一下正则 要求匹配到的位置不是开头,能够用 /(?!^)(?=(\d{3})+$)/g
来表示。 换种思路来想,能不能是以数字开头而后加上上面的条件呢,得出这个正则 /\d(?=(\d{3})+$)/g
,可是这个正则匹配的结果是 12,12,123
,发现这个正则匹配的不是位置而是字符,将数字换成了 ',' 能够得出结论,若是要求一个正则是匹配位置的话,那么全部的条件必须都是位置。分组主要是括号的使用
在分支结构中,括号是用来表示一个总体的,(p1|p2),好比要匹配下面的字符串
I love JavaScript
I love Regular Expression
复制代码
能够用正则/^I love (JavaScript|Regular Expression)$/
而不是 /^I love JavaScript|Regular Expression$/
表示一个总体还好比 /(abc)+/
一个或者多个 abc 字符串 上面这些使用 () 包起来的地方就叫作分组
'I love JavaScript'.match(/^I love (JavaScript|Regular Expression)$/)
// ["I love JavaScript", "JavaScript", index: 0, input: "I love JavaScript"]
复制代码
输出的数组第二个元素,"JavaScript" 就是分组匹配到的内容
好比咱们要用正则来匹配一个日期格式,yyyy-mm-dd,能够写出简单的正则/\d{4}-\d{2}-\d{2}/
,这个正则还能够改为分组形式的/(\d{4})-(\d{2})-(\d{2})/
这样咱们能够分别提取出一个日期的年月日,用 String 的 match 方法或者用正则的 exec 方法均可以
var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-08-09";
console.log( string.match(regex) );
// => ["2017-08-09", "2017", "08", "09", index: 0, input: "2017-08-09"]
复制代码
也能够用正则对象构造函数的全局属性 $1 - $9 来获取
var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-08-09";
regex.test(string); // 正则操做便可,例如
//regex.exec(string);
//string.match(regex);
console.log(RegExp.$1); // "2017"
console.log(RegExp.$2); // "08"
console.log(RegExp.$3); // "09"
复制代码
若是想要把 yyyy-mm-dd 替换成格式 mm/dd/yyyy 应该怎么作。 String 的 replace 方法在第二个参数里面能够用 $1 - $9 来指代相应的分组
var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-08-09";
var result = string.replace(regex, "$2/$3/$1");
console.log(result); // "08/09/2017"
等价
var result = string.replace(regex, function() {
return RegExp.$2 + "/" + RegExp.$3 + "/" + RegExp.$1;
});
console.log(result); // "08/09/2017"
等价
var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-08-09";
var result = string.replace(regex, function(match, year, month, day) {
return month + "/" + day + "/" + year;
});
console.log(result); // "08/09/2017"
复制代码
以前匹配日期的正则在使用的时候发现还有另外两种写法,一共三种
2017-08-09
2017/08/09
2017.08.09
复制代码
要匹配这三种应该怎么写正则,第一反应确定是把上面那个正则改一下/(\d{4})[-/.](\d{2})[-/.](\d{2})/
,把 - 改为 [-/.] 这三种均可以 看上去没问题,咱们多想一想就会发现,这个正则把 2017-08.09
这种字符串也匹配到了,这个确定是不符合预期的。 这个时候咱们就须要用到反向引用了,反向引用能够在匹配阶段捕获到分组的内容 /(\d{4})([-/.])(\d{2})\2(\d{2})/
var regex = /^((\d)(\d(\d)))\1\2\3\4$/;
var string = "1231231233";
console.log( regex.test(string) ); // true
console.log( RegExp.$1 ); // 123
console.log( RegExp.$2 ); // 1
console.log( RegExp.$3 ); // 23
console.log( RegExp.$4 ); // 3
复制代码
嵌套的括号以左括号为准
若是在正则里面引用了前面不存在的分组,这个时候正则会匹配字符自己,好比\1
就匹配\1
咱们有时候只是想用括号本来的功能而不想捕获他们。这个时候能够用(?:p)
表示一个非捕获分组
驼峰改短横
function dash(str) {
return str.replace(/([A-Z])/g, '-$1').toLowerCase();
}
复制代码
获取连接的 search 值连接:https://www.baidu.com?name=jawil&age=23
function getParamName(attr) {
let match = RegExp(`[?&]${attr}=([^&]*)`) //分组运算符是为了把结果存到exec函数返回的结果里
.exec(window.location.search)
//["?name=jawil", "jawil", index: 0, input: "?name=jawil&age=23"]
return match && decodeURIComponent(match[1].replace(/\+/g, ' ')) // url中+号表示空格,要替换掉
}
console.log(getParamName('name')) // "jawil"
复制代码
去掉字符串先后的空格
function trim(str) {
return str.replace(/(^\s*)|(\s*$)/g, "")
}
复制代码
判断一个数是不是质数
function isPrime(num) {
return !/^1?$|^(11+?)\1+$/.test(Array(num+1).join('1'))
}
复制代码
这里首先是把一个数字变成1组成的字符串,好比11就是 '1111111111' 11个1 而后正则分两部分,第一部分是匹配空字符串或者1,第二部分是先匹配两个或者多个1,非贪婪模式,那么先会匹配两个1,而后将匹配的两个1分组,后面就是匹配一个或者多个'2个1',就至关于整除2,若是匹配成功就证实不是质数,若是不成功就会匹配3个1,而后匹配多个3个1,至关于整除3,这样一直下去会一直整除到本身自己。若是仍是不行就证实这个数字是质数。
有这么一个字符串 'abbbc' 和这么一个正则 /ab{1,3}bbc/
/ab{1,3}bbc/.test('abbbc')
咱们一眼能够看出来是 true,可是 JavaScript 是怎么匹配的呢
例如咱们上面的例子,回溯的思想是,从问题的某一种状态(初始状态)出发,搜索从这种状态出发所能达到的全部“状态”,当一条路走到“尽头”的时候(不能再前进),再后退一步或若干步,从另外一种可能“状态”出发,继续搜索,直到全部的“路径”(状态)都试探过。这种不断“前进”、不断“回溯”寻找解的方法,就称做“回溯法”
贪婪和非贪婪的匹配都会产生回溯,不一样的是贪婪的是先尽可能多的匹配,若是不行就吐出一个而后继续匹配,再不行就再吐出一个,非贪婪的是先尽可能少的匹配。若是不行就再多匹配一个,再不行就再来一个 分支结构也会产生回溯,好比/^(test|te)sts$/.test('tests')
前面括号里面的匹配过程是先匹配到 test 而后继续日后匹配匹配到字符 s
的时候仍是成功的,匹配到 st
的时候发现不能匹配, 因此会回到前面的分支结构的其余分支继续匹配,若是不行的话再换其余分支。
读懂其余人写的正则也是一个很重要的方面。
结构:字符字面量、字符组、量词、锚字符、分组、选择分支、反向引用。
操做符:
操做符的优先级是从上到下,由高到低的,因此在分析正则的时候能够根据优先级来拆分正则,好比 /ab?(c|de*)+|fg/
由于括号是一个总体,因此/ab?()+|fg/
,括号里面具体是什么能够放到后面再分析
根据量词和管道符的优先级,因此a
, b?
, ()+
和管道符后面的f
, g
同理分析括号里面的c|de*
=> c
和d
, e*
综上,这个正则描述的是
以这种模式来分析,再复杂的正则均可以看懂。有一个可视化的
正则分析网站