正则表达式(regex or regexp) 通过检索特定模式的一个或多个匹配(即特定的ASCII或者unicode字符序列 ),从任何文本中提取信息,这是非常有用的。
应用范围:解析、替换字符串,数据格式转换,以及网页抓取。
有趣的是,一旦你学会了它的语法,几乎可以在所有编程语言中使用这个工具(JavaScript, Java, VB, C #, C / C++, Python, Perl, Ruby, Delphi, R, Tcl, and many others),它们之中只有微小的区别。
让我们来看看一些例子和解析。
基本语法
边界匹配 — ^ and $
-
^The 匹配任何以
The开始的字符串 -> Try it! -
end$ 匹配任何以
End结尾的字符串 -
^The end$ 准确地匹配字符串 (
The开始,End结尾) -
roar 匹配任何含有
roar的字符串
量词 — * + ? and {}
-
abc* 匹配
ab后跟零或多次c的字符串 [0, +∞] -> Try it! -
abc+ 匹配
ab后一次或多次c的字符串 [1, +∞] -
abc? 匹配
ab后零次或一次c的字符串 [0, 1] -
abc{2} 匹配
ab后2次c的字符串 -
abc{2,} 匹配
ab后2次或更多次c的字符串 -
abc{2,5} 匹配
ab后2到5次c的字符串 -
a(bc)* 匹配
a后零次或多次bc的字符串 -
a(bc){2,5} 匹配
a后2到5次bc的字符串
或— | or []
-
a(b|c) 匹配
a后跟b或c的字符串 -
a[b|c] 与上述一致
字符类 — \d \w \s and .
-
\d 匹配一个
数字-> Try it! -
\w 匹配一个
字符(字母、数字、字符、下划线) -> Try it! -
\s 匹配一个
空格(包括tabs、换行\n) -
. 匹配
任何非空字符(不包含换行\n等空字符) -> Try it! -
[\s\S] 匹配
任何字符
谨慎使用.元字符,因为字符类和否定字符类处理更快、更准确。
\d, \w 和 \s 分别使用 \D, \W 和 \S 表示它们的否定。
比如,\D将匹配与\d相反的字符。
- \D 匹配一个
非数字的字符 -> Try it!
为了正确的理解,你必须使用反斜杠\ 来转义字符^.[$()|*+?{\, 因为它们具有特殊的含义。
- $\d 匹配一个
$后跟一个数字的字符
注意,你还可以匹配一个不可打印的字符, 如tabs
\t, 换行\n,回车符\r。
修饰符
我们正在学习如果编写一个正则表达式,但是忘记了一个基本概念:修饰符
正则表达式通常是/abc/这样的形式,其中匹配模式由两个/分隔。我们可以在它们的最后指定一个以下这些值的标志(也可以将它们组合使用)。
- g (global): 第一个匹配后不返回结果,在上一次匹配结果之后继续检索,最后返回所有匹配项(全局匹配)。
- m (multi-line):启用时
^和 $ 将匹配行的开头和结尾,而不是整个字符串。 - i (insensitive): 使整个表达式不区分大小写(for instance
/aBc/iwould matchAbC)
es6新增
-
y (sticky):作用与
g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配结果的下一个位置开始。不同之处在于,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 var r = /a+_/y; // try again r.exec(s); // ['aaa_'] r.exec(s); // ['aa_'] r.sticky // true -
u (Unicode):“Unicode”模式,用来正确处理大于\uFFFF的Unicode字符。也就是说,可以正确处理4个字节的UTF-16编码。
var s = '𠮷'; /^.$/.test(s); // false /^.$/u.test(s); // true -
s:匹配意字符,我们知道
点(.)特殊字符代表任意字符,但是“行终止符”除外(eg.\n,\r,行分割符,段分隔符),s修饰符可包含所有,这被称为dotAll模式,即点(dot)代表一切字符。var s = '𠮷'; /.*/s.test(s); // true
中级语法
分组和捕获— ()
要理解?:则需要理解捕获分组和非捕获分组的概念:
()表示捕获分组,()会把每个分组里的匹配的值保存起来,使用$n(n是一个数字,表示第n个捕获组的内容);
(?:)表示非捕获分组,和捕获分组唯一的区别在于,非捕获分组匹配的值不会保存起来。
es6新增
- a(?< foo >bc) 使用
?<foo>给这个捕获组命名。-> Try it!
如果为捕获组命名(使用?<foo>), 我们将能够使用匹配结果的groups中查找捕获组的值,键值就是组名。
当我们从字符串或者数据中提取信息的时候,此运算符非常有用。在使用多个捕获组匹配数据时,
我们将使用匹配结果的索引来访问它们的值($n),也可以具名组groups.<name>访问
var string = '1999-12-31';
const matchObj = string.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/);
// ["1999-12-31", "1999", "12", "31", index: 0, input: "1999-12-31", groups: {day: "31", month: "12", year: "1999"}]
const newStr = string.replace(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/, '$<day>/$<month>/$<year>')
// 31/12/1999
const newStr2 = string.replace(/(\d{4})-(\d{2})-(\d{2})/, '$3/$2/$1')
// 31/12/1999
// 在 正则表达式内部 也可以使用“具名组匹配” \k<组名>
const RE_TWICE = /^(?<word>[a-z]+)!\k<word>$/
RE_TWICE.test('abc!abc') // true
RE_TWICE.test('abc!ab') // false
中括号— []
-
[abc] 匹配
a或b或c, 等同于a|b|c-> Try it! -
[a-c] 与上述一致
-
[a-fA-F0-9] 匹配一个
十六进制字符,不区分大小写。-> Try it! -
[0-9]% 匹配一个
%前为0到9的字符串 -
[^a-zA-Z] 匹配一个
没有从a-z或者A到Z的字母。这种情况下,^用作表示否定。-> Try it!
需要注意的是,在中括号表达式中,所有特殊字符(包括反斜杠
\)都会失去它们特殊的功能,因此,不要使用“转义”功能
贪婪和懒匹配(Greedy and Lazy match)
量词( * + {})是贪婪的匹配,所以它们会尽可能地通过提供的文本扩展匹配。
例如,使用<.+> 来匹配 <div>simple div</div>,它会返回整个文本<div> simple div</div>
为了仅匹配一个div标签,我们可以使用?来使它lazy match
- <.+?> 匹配
<和>内包含的一个或者多个字符,根据需要进行扩展。-> Try it!
那么,更好的正则方案应该避免使用., 赞成使用更严格的模式:
- <[^<>]+> 匹配
<和>内包含的任何字符。-> Try it!
高级语法
边界 — \b and \B
- \babc\b abc前后都无字符, 即执行
仅限整个单词匹配 -> Try it!
\b 表示边界(类似于^和$)的位置,其中一侧是单词字符(如\w),而另一侧不是单词字符(例如,它可能是字符串的开头或空格字符 eg: \b123)。
它同样有否定,\B。这匹配\b所有不匹配的位置,如果我们找到完全被单词字符包围的匹配模式, 则可以匹配。
- \Babc\B 执行
abc左右都被字符包围匹配 -> Try it!
返回引用— \1
-
([abc])\1 使用
\1返回与第一个捕获组相同的匹配项进行匹配 == ([abc])([abc]) -> Try it! -
([abc])([de])\2\1 与上述一致,使用
\2,\1返回与第二个捕获组,第一个捕获组相同的匹配项进行匹配 == ([abc])([de])([de])([abc]) , 以此类推-> Try it! -
(?< foo >[abc])\k< foo > 我们将名称foo放入捕获组中,可以用
\k引用它,结果与第一个正则一致 == ([abc])([abc]) -> Try it!
前瞻(前置断言)和后瞻(后置断言)— (?=) and (?<=)
firefox 目前不兼容,遇到过一次,请注意
你当然也可以使用否定运算符。
用法介绍
注:pattern 为RegExp的实例, str 为String的实例
| 用法 | 说明 | 返回值 |
|---|---|---|
| regexp.test(str) | 判断str是否包含匹配结果 | 包含返回true,不包含返回false |
| regexp.exec(str) | 根据regexp对str进行正则匹配 | 返回匹配结果数组,如匹配不到返回null ---与match的区别在于返回匹配信息更全 |
| str.match(regexp) | 根据regexp对str进行正则匹配 | 返回匹配结果数组,如匹配不到返回null |
| str.replace(regexp, newSubStr \ function) 详解 | 根据regexp / string对str进行正则匹配,把匹配结果替换为newSubStr \ function 的返回值 | 返回替换后的字符串【原字符串不变】 |
| str.search(regexp) | 根据regexp对str进行正则匹配 | 返回第一次匹配的位置 |
| str.split(regexp) | 以regexp为分隔符,对str切割为数组 | 返回切割后的数组 |
test/exec 注意事项
如果正则表达式设置了全局标志 /g,test() 的执行会改变正则表达式 lastIndex 属性。连续的执行 test()方法,后续的执行将会从 lastIndex 处开始匹配字符串,(exec() 同样改变正则本身的 lastIndex 属性值).
下面的实例表现了这种行为:
const digits = /\d+/g;
digits.test("Hello world! 123"); // true
digits.test("321"); // false
digits.test("321"); // true
你可以这样 hack:
const digits = /\d+/g;
digits.test("Hello world! 123"); // true
digits.lastIndex = 0;
digits.test("321"); // true
digits.lastIndex = 0;
digits.test("321"); // true
详情参考:MDN
replace详解
- 语法
str.replace(regexp|substr, newSubStr|function)
-
参数
-
regexp(pattern)一个RegExp对象或者其字面量。该正则所匹配的内容会被第二个参数的返回值替换掉。
-
substr(pattern)一个要被
newSubStr替换的字符串。其被视为一整个字符串,而不是一个正则表达式。仅仅是第一个匹配会被替换。 -
newSubStr(replacement)用于替换掉第一个参数在原字符串中的匹配部分的字符串
-
function(a, b, c, d)(replacement)一个用来创建新子字符串的函数,该函数的返回值将替换掉第一个参数匹配到的结果。
-
a:匹配项
-
b:匹配的捕获组
若无捕获组则无此参数,若多个捕获组则多个参数 b,c,d,e...;
若一个捕获组重复多次,则该捕获组的参数为最后一次匹配结果。例如:(\d)+
-
c:匹配项在原字符串中的索引
-
d:原字符串
最后两个参数永远为 匹配项索引和原字符串
-
-
-
如果你对 replace 还有困惑,可以看看下面的例题
总结
正如您所见,正则表达式应用领域很广,我确信你的开发生涯中至少见过一次以上规则,下面是它的应用列表:
- 数据验证(例如,检查时间字符串的格式是否正确)
- 数据抓取(尤其是网络抓取,查找包含特定单词集的所有页面,最终按特定顺序排列)
- 数据包装(将数据从“原始”格式转换为其他格式)
- 字符串分析(例如,捕获所有url get参数,捕获一组括号内的文本)
- 字符串替换(例如,即使在代码会话中使用一个公共IDE来将Java或C类)转换为相应的JSON对象{--替换“;”与“,”使其为小写,避免类型声明等)。
- 语法高亮、文件重命名、packet sniffing和许多其他涉及字符串的应用程序(其中数据不需要是文本的)
Have fun and do not forget to recommend the article if you liked it 💚
附录:replace例题
- 将下面两处空缺补全:
// define
(function(window) {
function fn(str) {
this.str = str;
}
fn.prototype.format = function () {
var arg = ____;
return this.str.replace(____, function (a, b) {
return arg[b] || '';
})
};
window.fn = fn;
})(window);
// use
(function() {
var t = new fn('<p><a href="{0}">{1}<a><span>{2}</span></p>');
console.log(t.format('http://www.yonyou.com', 'yonyou', 'Welcome'));
})();
// 这里就不给答案了,如果你理解了 replace 用法, 这太 easy 了。
- 通过正则将 87654321 整数转为 货币 $87,654,321
'87654321'.replace(/(\d)+?(?=(\d{3})+(?!\d))/g, function(a, b, c, d) {
return d < 2 ? ("$" + a + ",") : (a + ",");
})
// $87,654,321
// 请深入理解replace和正则后再理解该题
'87654321'.replace(/(\d{1,3})(?=(\d{3})+$)/g, function(a, b, c, d) {
return d < 2 ? ("$" + a + ",") : (a + ",");
})
// $87,654,321
'87654321'.replace(/\d{1,3}(?=(\d{3})+$)/g, '$&,') // $& 为匹配项;$1,$2...为捕获组
// 87,654,321
- 密码强度正则,最少6位,包括至少1个大写字母,1个小写字母,1个数字
/(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])^[0-9A-Za-z]{6,}$/.test('w44Y4S')
// 前面三个前置断言,针对开头^项的约束
/^.*(?=.{6,})(?=.*\d)(?=.*[A-Z])(?=.*[a-z])/.test('w44sYw')