We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
本篇是阅读 《JavaScript 正则表达式迷你书》 与 《正则表达式30分钟入门教程》 的个人笔记,用于快速查阅与总结。关于正则表达式的教程还有文章网络资源非常多,这本迷你书比较全面对学习正则提供很大帮助。还有正则逻辑可视化网站对理解看起来又长又乱的正则有很大帮助。
横向模糊匹配:一个正则可匹配的字符串的长度不是固定的,其实现的方式是使用量词。例如 {m,n},表示连续出现最少 m 次,最多 n 次。/a{1,4}/ 匹配A连续出现一次或四次。
{m,n}
/a{1,4}/
纵向模糊匹配:一个正则匹配的字符串具体到某一位字符时,它可以不是某个确定的字符存在多种可能。其实现的方式是使用字符组。譬如 [abc],表示该字符是可以字符 "a"、"b"、"c" 中的任何一个。/a[123]b/ 匹配"a1b,"a2b" 或 "a3b"。
[abc]
"a"
"b"
"c"
/a[123]b/
"a1b
"a2b"
"a3b"
[abc] 匹配一个字符。匹配范围使用 - 连接,[1-6] 匹配1到6范围。如果想匹配-需要用上转义符避免引擎认为是范围表示法 [1\-6] 匹配"1" "6" "-"。排除使用 ^ 符号,例如 [^abc] 表示除了"a" "b" "c"之外的任意一个字符。以下是简写形式列表:
[1-6]
[1\-6]
"1"
"6"
"-"
[^abc]
\d
\D
\w
\W
\s
\S
.
匹配任意字可以使用 [\d\D]、[\w\W]、[\s\S] 和 [^] 。
以下是量词列表:
{m,}
{m}
?
+
*
关于贪婪匹配与惰性匹配。这里有个例子:
var regex = /\d{2,4}/g var string = "11 22 333333" console.log( string.match(regex) ) // -> ["11", "22", "3333", "33"]
/\d{2,4}/ 表示匹配数字0到9出现两次至四次。上面例子的结果是贪婪匹配的结果,会尽可能多的匹配到4位数字。如果在量词后面加入一个?问号则改为惰性匹配:
/\d{2,4}/
var regex = /\d{2,4}?/g var string = "11 22 333333" console.log( string.match(regex) ) // -> ["11", "22", "33", "33", "33"]
/\d{2,5}?/ 表示匹配数字0到9出现两次至四次,但当匹配到两次时就不再往下继续匹配。对比最上面的例子贪婪匹配 "333333" console输出结果,惰性匹配会尽可能少的匹配。通过在量词后面加个问号就能实现惰性匹配:
/\d{2,5}?/
"333333"
{m,n}?
{m,}?
??
+?
*?
一个模式可以实现横向和纵向模糊匹配。而多选分支可以支持多个子模式任选其一。
var regex = /good|goodbye/g var string = "goodbye" console.log( string.match(regex) ) // -> ["good"]
/good|goodbye/g 表示匹配"good"和"goodbye"其中任何之一,但多选分支是惰性的,匹配到"good"后则不会匹配后面的字符。
/good|goodbye/g
"good"
"goodbye"
^
$
正则位置匹配,最常用的就是 ^ 与 $ 两个锚,分别匹配开头和结尾。多行匹配模式(即有修饰符 m)时匹配的是每行开头与结尾:
var result = "line1\nline2".replace(/^|$/gm, '#') console.log(result) /* #line1# #line2# */
\b
\B
\b 是单词边界,具体就是 \w 与 \W 之间的位置,也包括 \w 与 ^ 之间的位置,和 \w 与 $ 之间的位置。
var result = '[JS] JS'.replace(/\b/g, "#") console.log(result) // -> "[#JS#] #JS#"
这里匹配 \b 单词边界位置并且替换成 #,结果分析:
"["
"J"
"S"
"]"
J
\B 则是与 \b 相反,匹配非单词边界
var result = '[JS] JS'.replace(/\B/g, "#") console.log(result) // -> "#[J#S]# J#S"
(?=p)
(?!p)
(?:p)
(?=p),其中 p 是一个子模式,即 p 前面的位置。例如 (?=a),表示 "a" 字符前面的位置。而 (?!p) 与 (?=p) 相反。(?:p)则表示不捕获分组。
p
(?=a)
var result = "cat".replace(/(?=a)/g, '@') console.log(result) // -> "c@at" var result = "cat".replace(/(?!a)/g, '@') console.log(result) // -> "@ca@t@"
数字的千位分隔符表示法,首先写出(?=\d{3}$)匹配位置到最后三位前面,因为是多个三个数字一组所以可以使用量词+匹配。
(?=\d{3}$)
'12345678911'.replace(/(?=\d{3}$)/g, ',') // -> '12345678,911' '12345678911'.replace(/(?=(\d{3})+$)/g, ',') // -> '12,345,678,911'
这样写会有一个问题是,当数字尾数是3的倍数时会出现,123,456,789的情况。可以利用(?!^)匹配不是开头的位置。
,123,456,789
(?!^)
'123456789'.replace(/(?!^)(?=(\d{3})+$)/g, ',') // -> '123,456,789'
比较(p)和(?:p),前者是捕获分组,后者不捕获,区别在于正则表达式匹配输入字符串之后所获得的匹配的(数)组当中没有(?:p)匹配的部分。
(p)
const m = "abcabc".match(/(?:a)(b)(c)/) //结果 ["abc", "b", "c"] // m[0] 是/(?:a)(b)(c)/匹配到的整个字符串,这里包括了a // m[1] 是捕获组1,即(b)匹配的子字符串substring or sub sequence // m[2] 是捕获组2,即(c)匹配到的
如果分组后面有量词的话例如/(\d)+/,那么分组捕获的是数据是最后一次的匹配。
/(\d)+/
var regex = /(\d)+/ var string = "12345" console.log( string.match(regex) ) // -> ["12345", "5", index: 0, input: "12345"]
括号内的正则是一个整体,即提供子表达式。
var regex = /(ab)+/g var string = "ababa abbb ababab" console.log( string.match(regex) ) // -> ["abab", "ab", "ababab"] var regex = /^I love (milk|juice)$/ console.log( regex.test("I love milk") ) // -> true console.log( regex.test("I love juice") ) // -> true
在匹配过程中给括号每一个分组都开辟一个空间,用来存储每一个分组匹配到的数据。下面这个例子是匹配提取年月日。可以使用字符串的 match 方法或者正则的 exec 方法。
match
exec
var regex = /(\d{4})-(\d{2})-(\d{2})/ var string = "2017-06-12" console.log( string.match(regex) ) // -> ["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12"] var regex = /(\d{4})-(\d{2})-(\d{2})/ var string = "2017-06-12" console.log( regex.exec(string) ) // -> ["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12"] console.log(RegExp.$1); // "2017" console.log(RegExp.$2); // "06" console.log(RegExp.$3); // "12"
match 返回的一个数组,第一个元素是整体匹配结果,然后是各个分组(括号里)匹配的内容,然后是匹配下标,最后是输入的文本。另外,正则表达式是否有修饰符 g,match 返回的数组格式是不一样的。同时可以用 RegExp 构造函数的全局属性 $1 至 $9 来获取匹配的内容。
g
RegExp
$1
$9
在字符串的 replace 方法第二个参数中如果是字符串则 $1 至 $9 分组其实指向的就是 RegExp 构造函数中的全局属性。如果第二个参数是一个方法那么方法里也可直接用 RegExp 访问全局属性。或者方法接收匹配到的分组内容参数作为参数。
replace
var regex = /(\d{4})-(\d{2})-(\d{2})/; var string = "2017-06-12"; // 三种等价写法 var result = string.replace(regex, "$2/$3/$1"); var result = string.replace(regex, function () { return RegExp.$2 + "/" + RegExp.$3 + "/" + RegExp.$1; }); var result = string.replace(regex, function (match, year, month, day) { return month + "/" + day + "/" + year; }); console.log(result); // -> "06/12/2017"
正则表达式里的/1,表示第一个分组,/2和/3等等含义以此类推。下面是一个运用到反向引用的例子,例如一个正则需要匹配 95/12/14 或 95-12-14 或 95.12.14。
/1
/2
/3
const regex = /\d{2}(-|\/|\.)\d{2}(-|\/|\.)\d{2}/ const string1 = '95/12/14' const string2 = '95-12-14' const string3 = '95.12.14' regex.test(string1); // -> true regex.test(string2); // -> true regex.test(string3); // -> true const errorString = '95/12-14' regex.test(errorString); // -> true
这个正则会出现一个问题,匹配 95/12-14 或 95.12/14 这种前后符号不一情况也测试通过。所以这里就利用 \1 代替后面的 (-|\/|\.) 匹配分组。不管前面分组匹配到什么字符,后面 \1 都匹配前面匹配到的那个相同的字符。
\1
(-|\/|\.)
const regex = /\d{2}(-|\/|\.)\d{2}\1\d{2}/ const errorString = '95/12-14' regex.test(errorString); // -> false
之前的分组引用与反向引用都会捕获到匹配的数据,也因此称为捕获型分组和捕获型分支。如果只想要括号最原始的功能,但不会引用它(既不在 API 里引用,也不在正则里反向引用)。此时可以使用非捕获括号 (?:p) 和 (?:p1|p2|p3)
(?:p1|p2|p3)
// 原正则是 /(ab)+/g const regex = /(?:ab)+/g const string = "ababa abbb ababab" console.log( string.match(regex) ) // -> ["abab", "ab", "ababab"] // 原正则是 /^I love (JavaScript|Regular Expression)$/ const regex = /^I love (?:JavaScript|Regular Expression)$/ console.log( regex.test("I love JavaScript") ) // -> true console.log( regex.test("I love Regular Expression") ) // -> true
正则这门语言跟其他语言有一点不同,它通常就是一大堆字符,而没有所谓“语句”的概念。如何能正确地把一大串正则拆分成一块一块的,成为了破解“天书”的关键。
在 JS 中正则的结构有字符字面量、字符组、量词、锚、分组、选择分支、反向引用。
\n
\.
[0-9]
[^0-9]
a{1,3}
a+
(?=\d)
(ab)+
(?:ab)+
其中涉及到的操作符
\
(…)
(?:…)
(?=…)
(?!…)
[…]
所谓元字符,就是正则中有特殊含义的字符。所有结构里,用到的元字符总结如下:
^、$、.、*、+、?、|、\、/、(、)、[、]、{、}、=、!、:、-
|
/
(
)
[
]
{
}
=
!
:
-
var string = "^$.*+?|\\/[]{}=!:-,"; var regex = /\^\$\.\*\+\?\|\\\/\[\]\{\}\=\!\:\-\,/; console.log( regex.test(string) ); // => true
相关API
String.search() String.split() String.match() String.replace() RegExp.test() RegExp.exec()
The text was updated successfully, but these errors were encountered:
No branches or pull requests
本篇是阅读 《JavaScript 正则表达式迷你书》 与 《正则表达式30分钟入门教程》
的个人笔记,用于快速查阅与总结。关于正则表达式的教程还有文章网络资源非常多,这本迷你书比较全面对学习正则提供很大帮助。还有正则逻辑可视化网站对理解看起来又长又乱的正则有很大帮助。
字符匹配篇
匹配模式
横向模糊匹配:一个正则可匹配的字符串的长度不是固定的,其实现的方式是使用量词。例如
{m,n}
,表示连续出现最少 m 次,最多 n 次。/a{1,4}/
匹配A连续出现一次或四次。纵向模糊匹配:一个正则匹配的字符串具体到某一位字符时,它可以不是某个确定的字符存在多种可能。其实现的方式是使用字符组。譬如
[abc]
,表示该字符是可以字符"a"
、"b"
、"c"
中的任何一个。/a[123]b/
匹配"a1b
,"a2b"
或"a3b"
。字符组
[abc]
匹配一个字符。匹配范围使用 - 连接,[1-6]
匹配1到6范围。如果想匹配-需要用上转义符避免引擎认为是范围表示法[1\-6]
匹配"1"
"6"
"-"
。排除使用 ^ 符号,例如[^abc]
表示除了"a"
"b"
"c"
之外的任意一个字符。以下是简写形式列表:\d
\D
\w
\W
\s
\S
.
匹配任意字可以使用 [\d\D]、[\w\W]、[\s\S] 和 [^] 。
量词
以下是量词列表:
{m,}
{m}
?
+
*
关于贪婪匹配与惰性匹配。这里有个例子:
/\d{2,4}/
表示匹配数字0到9出现两次至四次。上面例子的结果是贪婪匹配的结果,会尽可能多的匹配到4位数字。如果在量词后面加入一个?
问号则改为惰性匹配:/\d{2,5}?/
表示匹配数字0到9出现两次至四次,但当匹配到两次时就不再往下继续匹配。对比最上面的例子贪婪匹配"333333"
console输出结果,惰性匹配会尽可能少的匹配。通过在量词后面加个问号就能实现惰性匹配:{m,n}?
{m,n}
{m,}?
{m,}
??
?
+?
+
*?
*
多选分支
一个模式可以实现横向和纵向模糊匹配。而多选分支可以支持多个子模式任选其一。
/good|goodbye/g
表示匹配"good"
和"goodbye"
其中任何之一,但多选分支是惰性的,匹配到"good"
后则不会匹配后面的字符。位置匹配篇
^
和$
正则位置匹配,最常用的就是
^
与$
两个锚,分别匹配开头和结尾。多行匹配模式(即有修饰符 m)时匹配的是每行开头与结尾:\b
和\B
\b
是单词边界,具体就是\w
与\W
之间的位置,也包括\w
与^
之间的位置,和\w
与$
之间的位置。这里匹配
\b
单词边界位置并且替换成 #,结果分析:"["
与"J"
,是\W
与\w
之间的位置。"S"
与"]"
,是\w
与\W
之间的位置。J
,是\W
与\w
之间的位置。"S"
与 结尾,是\w
与$
之间的位置。\B
则是与\b
相反,匹配非单词边界(?=p)
(?!p)
(?:p)
(?=p)
,其中p
是一个子模式,即p
前面的位置。例如(?=a)
,表示"a"
字符前面的位置。而(?!p)
与(?=p)
相反。(?:p)
则表示不捕获分组。数字的千位分隔符表示法,首先写出
(?=\d{3}$)
匹配位置到最后三位前面,因为是多个三个数字一组所以可以使用量词+匹配。这样写会有一个问题是,当数字尾数是3的倍数时会出现
,123,456,789
的情况。可以利用(?!^)
匹配不是开头的位置。比较
(p)
和(?:p)
,前者是捕获分组,后者不捕获,区别在于正则表达式匹配输入字符串之后所获得的匹配的(数)组当中没有(?:p)匹配的部分。括号分组
如果分组后面有量词的话例如
/(\d)+/
,那么分组捕获的是数据是最后一次的匹配。分组引用
括号内的正则是一个整体,即提供子表达式。
提取数据
在匹配过程中给括号每一个分组都开辟一个空间,用来存储每一个分组匹配到的数据。下面这个例子是匹配提取年月日。可以使用字符串的
match
方法或者正则的exec
方法。match
返回的一个数组,第一个元素是整体匹配结果,然后是各个分组(括号里)匹配的内容,然后是匹配下标,最后是输入的文本。另外,正则表达式是否有修饰符g
,match
返回的数组格式是不一样的。同时可以用RegExp
构造函数的全局属性$1
至$9
来获取匹配的内容。替换数据
在字符串的
replace
方法第二个参数中如果是字符串则$1
至$9
分组其实指向的就是RegExp
构造函数中的全局属性。如果第二个参数是一个方法那么方法里也可直接用RegExp
访问全局属性。或者方法接收匹配到的分组内容参数作为参数。反向引用
正则表达式里的
/1
,表示第一个分组,/2
和/3
等等含义以此类推。下面是一个运用到反向引用的例子,例如一个正则需要匹配 95/12/14 或 95-12-14 或 95.12.14。这个正则会出现一个问题,匹配 95/12-14 或 95.12/14 这种前后符号不一情况也测试通过。所以这里就利用
\1
代替后面的(-|\/|\.)
匹配分组。不管前面分组匹配到什么字符,后面\1
都匹配前面匹配到的那个相同的字符。非捕获括号
之前的分组引用与反向引用都会捕获到匹配的数据,也因此称为捕获型分组和捕获型分支。如果只想要括号最原始的功能,但不会引用它(既不在 API 里引用,也不在正则里反向引用)。此时可以使用非捕获括号
(?:p)
和(?:p1|p2|p3)
正则拆分
正则这门语言跟其他语言有一点不同,它通常就是一大堆字符,而没有所谓“语句”的概念。如何能正确地把一大串正则拆分成一块一块的,成为了破解“天书”的关键。
结构和操作符
在 JS 中正则的结构有字符字面量、字符组、量词、锚、分组、选择分支、反向引用。
\n
匹配换行符,又比如\.
匹配小数点。[0-9]
,表示匹配一个数字。也有\d
的简写形式。另外还有反义字符组,表示可以是除了特定字符之外任何一个字符,比如
[^0-9]
,表示一个非数字字符,也有\D
的简写形式。a{1,3}
表示 "a" 字符连续出现 3 次。另外还有常见的简写形式,比如
a+
表示 "a" 字符连续出现至少一次。\b
匹配单词边界,又比如(?=\d)
表示数字前面的位置。(ab)+
,表示 "ab" 两个字符连续出现多次,也可以使用非捕获分组(?:ab)+
。其中涉及到的操作符
\
(…)
、(?:…)
、(?=…)
、(?!…)
、[…]
{m}
、{m,n}
、{m,}
、?
、*
、+
^
、$
、\
元字符、一般字符所谓元字符,就是正则中有特殊含义的字符。所有结构里,用到的元字符总结如下:
^
、$
、.
、*
、+
、?
、|
、\
、/
、(
、)
、[
、]
、{
、}
、=
、!
、:
、-
相关API
The text was updated successfully, but these errors were encountered: