# 正则表达式是什么
正则表达式是用于匹配字符串中字符组合的模式。在 JavaScript 中,正则表达式也是对象。这些模式被用于 JavaScript 的 RegExp
方法中,例如 test()
和 exec()
,以及字符串的 match()
、 replace()
、 search()
和 split()
方法。
# 创建正则表达式
正则表达式可以通过两种方式在 JavaScript 中创建:
-
字面量:使用两个斜杠包围的模式。
let regex = /ab+c/;
-
构造函数:使用
RegExp
对象的构造函数。let regex = new RegExp("ab+c");
# 常用的正则表达式语法元素
-
锚点
^
匹配输入字符串的开始位置。$
匹配输入字符串的结束位置。
-
字符类
.
匹配除换行符以外的任何单字符。\d
匹配一个数字字符。等价于[0-9]
。\D
匹配一个非数字字符。等价于[^0-9]
。\w
匹配字母、数字、下划线。等价于[A-Za-z0-9_]
。\W
匹配非字母、数字、下划线。\s
匹配任何空白字符,包括空格、制表符、换行符等。\S
匹配任何非空白字符。[abc]
匹配任何一个指定字符(在此例中为 'a'、'b' 或 'c')。[^abc]
匹配任何不在指定集合中的字符。
-
量词
*
匹配前面的表达式 0 次或更多次。+
匹配前面的表达式 1 次或更多次。?
匹配前面的表达式 0 次或 1 次。{n}
精确匹配 n 次。{n,}
至少匹配 n 次。{n,m}
最少匹配 n 次且最多 m 次。
-
位置和断言
\b
匹配一个单词边界。\B
匹配非单词边界。(?=...)
正向前瞻,匹配的字符串后面必须紧跟着...
。(?!...)
负向前瞻,匹配的字符串后面不能紧跟着...
。
-
分组和引用
(xyz)
匹配并捕获括号内的表达式,也可以通过\1
、\2
等引用。(?:xyz)
只匹配但不捕获,不分配组号。
-
选择
|
匹配两个或多个分支选择的任意一个。
# 综合示例
假设我们需要从文本中找出所有格式为 (XXX) XXX-XXXX
的美国电话号码:
let text = "Contact numbers are (555) 123-4567 and (321) 987-6543."; | |
let phoneRegex = /\(\d{3}\) \d{3}-\d{4}/g; | |
let matches = text.match(phoneRegex); | |
console.log(matches); // 输出: ["(555) 123-4567", "(321) 987-6543"] |
在这个示例中, \(\d{3}\)
匹配三位数字加括号,空格是字面意义上的空格, \d{3}
和 \d{4}
分别匹配电话号码中的三位和四位数字。
# 常用场景
# 切分字符串
用正则表达式切分字符串比用固定的字符更灵活,请看正常的切分代码:
"a b c".split(" "); // ['a', 'b', '', '', 'c'] |
嗯,无法识别连续的空格,用正则表达式试试:
"a b c".split(/\s+/); // ['a', 'b', 'c'] |
无论多少个空格都可以正常分割。加入 ,
试试:
"a,b, c d".split(/[\s\,]+/); // ['a', 'b', 'c', 'd'] |
再加入 ;
试试:
"a,b;; c d".split(/[\s\,\;]+/); // ['a', 'b', 'c', 'd'] |
如果用户输入了一组标签,下次记得用正则表达式来把不规范的输入转化成正确的数组。
# 分组
除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用 ()
表示的就是要提取的分组(Group)。比如:
^(\d{3})-(\d{3,8})$
分别定义了两个组,可以直接从匹配的字符串中提取出区号和本地号码:
var re = /^(\d{3})-(\d{3,8})$/; | |
re.exec("010-12345"); // ['010-12345', '010', '12345'] | |
re.exec("010 12345"); // null |
如果正则表达式中定义了组,就可以在 RegExp
对象上用 exec()
方法提取出子串来。
exec()
方法在匹配成功后,会返回一个 Array
,第一个元素是正则表达式匹配到的整个字符串,后面的字符串表示匹配成功的子串。
exec()
方法在匹配失败时返回 null
。
提取子串非常有用。来看一个更凶残的例子:
var re = | |
/^(0[0-9]|1[0-9]|2[0-3]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])$/; | |
re.exec("19:05:30"); // ['19:05:30', '19', '05', '30'] |
这个正则表达式可以直接识别合法的时间。但是有些时候,用正则表达式也无法做到完全验证,比如识别日期:
var re = /^(0[1-9]|1[0-2]|[0-9])-(0[1-9]|1[0-9]|2[0-9]|3[0-1]|[0-9])$/; |
对于 '2-30'
, '4-31'
这样的非法日期,用正则还是识别不了,或者说写出来非常困难,这时就需要程序配合识别了。
# 贪婪匹配
需要特别指出的是,正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。举例如下,匹配出数字后面的 0
:
var re = /^(\d+)(0*)$/; | |
re.exec("102300"); // ['102300', '102300', ''] |
由于 \d+
采用贪婪匹配,直接把后面的 0
全部匹配了,结果 0*
只能匹配空字符串了。
我们想要的是,前面的 ^(\d+)
部分匹配 1023,后面的 (0*)
匹配 00。
必须让 \d+
采用非贪婪匹配(也就是尽可能少匹配),才能把后面的 0
匹配出来,加个 ?
就可以让 \d+
采用非贪婪匹配:
var re = /^(\d+?)(0*)$/; | |
re.exec("102300"); // ['102300', '1023', '00'] |
# 全局搜索
JavaScript 的正则表达式还有几个特殊的标志,最常用的是 g
,表示全局匹配:
var r1 = /test/g; | |
// 等价于: | |
var r2 = new RegExp("test", "g"); |
全局匹配可以多次执行 exec()
方法来搜索一个匹配的字符串。当我们指定 g
标志后,每次运行 exec()
,正则表达式本身会更新 lastIndex
属性,表示上次匹配到的最后索引:
var s = "JavaScript, VBScript, JScript and ECMAScript"; | |
var re = /[a-zA-Z]+Script/g; | |
// 使用全局匹配: | |
re.exec(s); // ['JavaScript'] | |
re.lastIndex; // 10 | |
re.exec(s); // ['VBScript'] | |
re.lastIndex; // 20 | |
re.exec(s); // ['JScript'] | |
re.lastIndex; // 29 | |
re.exec(s); // ['ECMAScript'] | |
re.lastIndex; // 44 | |
re.exec(s); //null,直到结束仍没有匹配到 |
全局匹配类似搜索,因此不能使用 /^...$/
,那样只会 最多匹配一次 。
正则表达式还可以指定 i
标志,表示忽略大小写; m
标志,表示执行多行匹配。