正则表达式是来匹配一个字符串的。"Regular Expression" 这个词太长了,我们通常使用它的缩写 "regex" 或者 "regexp"。 正则表达式可以被用来替换字符串中的文本、验证表单、基于模式匹配从一个字符串中提取字符串等等。
从现在开始,告别copy正则表达式!
在我们编码过程中,正则表达式是我们常来顾客,尤其是表单字段的校验。为了图方便,常常的做法就是去网上进货,然后作为中间商卖给表单。这种做法虽然方便,但是只能满足普通客户(表单)的需求,如果遇到一个大客户需要定制产品(特性的校验规则),到时我们再去学习如何制作就来不及了,客户不等人,失去了客户不说(项目延期),有可能还有扣你尾款(挨上级批斗);
一、基础知识
基本语法
/pattern/[modifiers];
- pattern:模式
- modifiers:修饰符
修饰符
修饰符 可以在全局搜索中不区分大小写:
修饰符 | 描述 |
---|---|
i | 执行对大小写不敏感的匹配。 |
g | 执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)。 |
m | 执行多行匹配。 |
示例:
var str = 'aBc Abcd abcde';
str.match(/bcd/); // ["bcd"]
str.match(/bcd/g); // ["bcd", "bcd"]
str.match(/abc/g); // ["abc"]
str.match(/abc/gi); // ["aBc", "Abc", "abc"]
我们在使用过程中,大多数情况都是需要全局匹配的,大小写是否敏感需要根据实际情况来看。
当同时使用多个描述符时,描述的顺序无要求:
str.match(/abc/gi); // ["aBc", "Abc", "abc"]
str.match(/abc/ig); // ["aBc", "Abc", "abc"]
描述字符
根据正则表达式语法规则,大部分字符仅能够描述自身,这些字符被称为普通字符,如所有的字母、数字等。
元字符就是拥有动态功能的特殊字符,需要加反斜杠进行标识,以便与普通字符和转义字符进行区别,JavaScript 正则表达式支持的元字符如表所示。
元字符 | 描述 |
---|---|
. | 查找单个字符,除了换行和行结束符 |
\w | 查找单词字符 |
\W | 查找非单词字符 |
\d | 查找数字 |
\D | 查找非数字字符 |
\s | 查找空白字符 |
\S | 查找非空白字符 |
\b | 匹配单词边界 |
\B | 匹配非单词边界 |
\n | 查找换行符 |
\f | 查找换页符 |
\r | 查找回车符 |
\t | 查找制表符 |
\v | 查找垂直制表符 |
\xxx | 查找以八进制数 xxxx 规定的字符 |
\xdd | 查找以十六进制数 dd 规定的字符 |
\uxxxx | 查找以十六进制 xxxx规定的 Unicode 字符 |
示例:
var str = "hello word 12a3";
str.match(/./gi); // ["h", "e", "l", "l", "o", " ", "w", "o", "r", "d", " ", "1", "2", "a", "3"]
str.match(/\d/gi); // ["1", "2", "3"]
str.match(/\D/gi); // ["h", "e", "l", "l", "o", " ", "w", "o", "r", "d", " ", "a"]
str.match(/\w/gi); // ["h", "e", "l", "l", "o", "w", "o", "r", "d", "1", "2", "a", "3"]
str.match(/\W/gi); // [" ", " "]
str.match(/\s/gi); // [" ", " "]
str.match(/\S/gi); // ["h", "e", "l", "l", "o", "w", "o", "r", "d", "1", "2", "a", "3"]
str.match(/\b/gi); // ["", "", "", "", "", ""]
str.match(/\B/gi); // ["", "", "", "", "", "", "", "", "", ""]
var str = '你好世界! Hello word!'
// 匹配任意 ASCII 字符
str.match(/[\u0000-\u00ff]/g); // ["!", " ", "H", "e", "l", "l", "o", " ", "w", "o", "r", "d", "!"]
// 匹配任意双字节的汉字
str.match(/[^\u0000-\u00ff]/g); // ["你", "好", "世", "界"]
// 匹配大写字母
str.match( /[\u0041-\u004A]/g); // ["H"]
重复匹配
可以对于某个内容进行多次匹配
量词 | 描述 |
---|---|
n+ | 匹配任何包含至少一个 n 的字符串 |
n* | 匹配任何包含零个或多个 n 的字符串 |
n? | 匹配任何包含零个或一个 n 的字符串 |
n{x} | 匹配包含 x 个 n 的序列的字符串 |
n{x,y} | 匹配包含最少 x 个、最多 y 个 n 的序列的字符串 |
n{x,} | 匹配包含至少 x 个 n 的字符串 |
示例:
var str = 'Hello helllo hehello hehehelllloooo'
str.match(/he/gi); // ["He", "He", "He", "he", "He", "he", "he", "he"]
str.match(/(he)+/gi); // ["He", "He", "Hehe", "Hehehehe"]
str.match(/(he)*/gi); // ["He", "", "", "", "", "He", "", "", "", "", "", "Hehe", "", "", "", "", "Hehehehe", "", "", "", "", "", "", "", "", ""]
str.match(/(he)?/gi); // ["He", "", "", "", "", "He", "", "", "", "", "", "He", "he", "", "", "", "", "He", "he", "he", "he", "", "", "", "", "", "", "", "", ""]
str.match(/(he){1}/gi); // ["He", "He", "He", "he", "He", "he", "he", "he"]
str.match(/(he){2}/gi); // ["Hehe", "Hehe", "hehe"]
str.match(/(he){2,}/gi); // ["Hehe", "Hehehehe"]
str.match(/(he){3,4}/gi); // ["Hehehehe"]
str.match(/(he)+l+/gi); // ["Hell", "Helll", "Hehell", "Hehehehellll"]
str.match(/(he)+l{3,}/gi); // ["Helll", "Hehehehellll"]
通过上面的例子,我们可以发现几个不同的用法可以得到相同的结果:
n+
等同于n{1,}
n?
等同于n{0,1}
n*
等同于n{0,}
边界量词
匹配模式的位置
量词 | 描述 |
---|---|
^ | 匹配开头,在多行检测中,会匹配一行的开头 |
$ | 匹配结尾,在多行检测中,会匹配一行的结尾 |
示例:
var str = 'abc ABC';
/^abc/gi.exec(str); // ['abc']
/abc$/gi.exec(str); // ['ABC']
/abc/gi.exec(str); // ['abc']
如果不添加^和$,则默认从开头匹配
匹配范围
表达式 | 描述 | ||
---|---|---|---|
[abc] | 查找方括号之间的任何字符。 | ||
[0-9] | 查找任何从 0 至 9 的数字。 | ||
(x\ | y) | 查找任何以\ | 分隔的选项。即x或y |
示例:
var str = 'Hello RegExp 369'
str.match(/[2-8]/gi); // ["3", "6"]
str.match(/[el]/gi); // ["e", "l", "l", "e", "E"]
str.match(/[x|5|6]/gi); // ["x", "6"]
str.match(/[a-h]/gi); // ["H", "e", "e", "g", "E"]
// 你也可以多个一起使用
str.match(/[2-8a-h]/gi); // ["H", "e", "e", "g", "E", "3", "6"]
转义字符
通过上面的学习我们可以看到,在正则表达式中,通过使用一些特殊字符,可以表示不同的匹配模式。如:+、{}、^、? 等。那么当我们需要匹配这些特殊字符怎么办呢?比如说:匹配‘1 + 2 = 3’ 中的 "+",此时我们就需要对“+”进行转义,即在需要转义的字符前面加上“\”。
var str = '1 + 1 = 3'
str.match(/\+/gi); // ["+"]
如果只需要匹配一个“+”,当你不进行转义时会报错:
当我们需要匹配以下特殊字符时,我们需要进行转义:
$
、 (
、 )
、 *
、 +
、 .
、 [
、 ]
、 ?
、 \
、 ^
、 {
、 }
、 |
二、断言
假设有这样一个场景,需要在"今日18:00-20:00全场5折,洗衣液只要¥19,不要错过哦"中匹配出价格。
价格是由数字组成,如果我们只通过数字匹配的话,会把其他信息也匹配进去:
var str = '今日18:00-20:00全场5折,洗衣液只要¥19,不要错过哦';
str.match(/\d+/gi); // ["18", "00", "20", "00", "5", "19"]
显然只通过数字是不行的,可以注意到,在¥符号后面的才是价格,其他的都不是,如果有什么方法可以匹配指定内容的后面就好了。答案就是断言:
var str = '今日18:00-20:00全场5折,洗衣液只要¥19,不要错过哦';
str.match(/(?<=¥)\d+/gi); // ["19"]
断言分为4种:
符号 | 描述 | 含义 |
---|---|---|
reg(?=exp) | 正向先行断言 | 匹配reg,且后面内容满足exp |
reg(?!exp) | 负向先行断言 | 匹配reg,且后面内容不满足exp |
(?<=exp)reg | 正向后发断言 | 匹配reg,且前面内容满足exp |
(?<!exp)reg | 负向后发断言 | 匹配reg,且前面内容不满足exp |
正向先行断言
形如 A(?=B)
的形式,表示匹配到A,且A的后面是B的内容。
var str = 'I scream, you scream, we all scream for ice-cream!'
// 匹配scream前面的一个单词
str.match(/\w+(?=\sscream)/gi); // ["I", "you", "all"]
负向先行断言
形如 A(?!B)
的形式,表示匹配到A,且A前面的内容不能满足B。
var str = 'I scream, you scream, we all scream for ice-cream!';
// 匹配scream单词,且后面不能是空格,
str.match(/scream(?!\s)/gi); // ["scream", "scream"] 只能匹配到第一和第二个
正向后发断言
形如 (?<=B)A
的形式,表示匹配到A,且A的前面满足B。
var str = 'I scream, you scream, we all scream for ice-cream!?'
// 匹配scream后的单词
str.match(/(?<=scream\s)\w+/gi); ["for"]
负向后发断言
形如 (?<!B)A
的形式,表示匹配到A,且A前面的不满足B。
var str = 'I scream, you scream, we all scream for ice-cream!';
// 匹配cream,且前面不能为字母
str.match(/(?<!\w)cream/gi); // ["cream"] 只能匹配到ice-cream中的cream
可能很多人看了之后很容易把这几个记忆混淆,这里教大家一个简单的方法理解与记忆:
- 断言(exp)写在后面就是匹配后面的内容,写在前面就是匹配前面的内容
- 正向表示满足该条件(符号
=
),负向表示不满足该条件(符号!
)
欢迎访问我的个人网站(相信你会喜欢上我的风格):www.dengzhanyong.com
关注我的个人公众号【前端筱园】,不错过我的每一篇推送
三、常用的正则表达式
- 正整数:
^\d+$
- 负整数:
^-\d+$
- 电话号码:
^+?[\d\s]{3,}$
- 电话代码:
^+?[\d\s]+(?[\d\s]{10,}$
- 整数:
^-?\d+$
- 用户名:
^[\w\d_.]{4,16}$
- 字母数字字符:
^[a-zA-Z0-9]*$
- 带空格的字母数字字符:
^[a-zA-Z0-9 ]*$
- 密码:
^(?=^.{6,}$)((?=.*[A-Za-z0-9])(?=.*[A-Z])(?=.*[a-z]))^.*$
- 电子邮件:
^([a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4})*$
- IPv4 地址:
^((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))*$
- 小写字母:
^([a-z])*$
- 大写字母:
^([A-Z])*$
- 网址:
^(((http|https|ftp):\/\/)?([[a-zA-Z0-9]\-\.])+(\.)([[a-zA-Z0-9]]){2,4}([[a-zA-Z0-9]\/+=%&_\.~?\-]*))*$
- VISA 信用卡号码:
^(4[0-9]{12}(?:[0-9]{3})?)*$
- 日期 (MM/DD/YYYY):
^(0?[1-9]|1[012])[- /.](0?[1-9]|[12][0-9]|3[01])[- /.](19|20)?[0-9]{2}$
- 日期 (YYYY/MM/DD):
^(19|20)?[0-9]{2}[- /.](0?[1-9]|1[012])[- /.](0?[1-9]|[12][0-9]|3[01])$
......
四、正则表达式方法
test()
test()
方法用于检测一个字符串是否匹配某个模式,如果字符串中含有匹配的文本,则返回 true,否则返回 false。
exec()
exec()
方法用于检索字符串中的正则表达式的匹配。该函数返回一个数组,其中存放匹配的结果。如果未找到匹配,则返回值为 null。
其他方法使用正则表达式
match()
match()
方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。
replace()
replace()
方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。
示例:
// 将所有的数字替换为*
var text = 'aaa126bbb34278ccc23';
text.replace(/\d/gi, '*'); // "aaa***bbb*****ccc**"
search()
search()
方法使用表达式来搜索匹配,然后返回匹配的位置。
示例:
// 获取至少出现两个连续数字的位置
var text = 'ab1cfd3ff452de7532';
text.search(/\d{2,}/gi); // 9
五、如何写出高效的正则表达式
- 误匹配
对于
+
、*
、?
这几个符号需要根据实际场景选择合适的使用,不要把他们混淆 - 漏匹配
如需要匹配18位的身份证号,如果这样写
\d{18}
就会出现漏匹配的情况,因为身份证的最后一位可能是 X ,可以这样改进:\d{17}(X|x|\d) - 明确
通常越简单的正则匹配到的结果就越多,还是拿身份证号来举例,18个0也满足上面的匹配的条件,但是这很明显不是一个省份证号。为了得到更加准确的匹配结果,这就需要要求我们的正则更加明确。
六、实战演练
我们来写一个匹配身份证号码的正则,首先需要了解身份证号码的结构。在很久前我写过一篇文章【你知道身份证是如何防伪的吗?】,这里我就不详细讲解了。
地址码长度为6,第一位1-9,后5位0-9
/^[1-9]\d{5}/
年份码长度为4,前两位可能是18、19、20,后两位都是0-9
/(18|19|20)\d{2}/
月份码两位01-12,日期码2位01-31
/((0[1-9])|1[0-2])(([0-2][1-9])|10|20|30|31)/
顺序码是3位0-9的数字
/\d{3}/
校验码1位可能是0-9或者X,X也可能是小写x
/\d{17}(X|\d|x)$/
也可以这样写
/\d{17}[0-9Xx]$/
最后把他们组合起来
/^[1-9]\d{5}(18|19|20|(3\d))\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/
通过这个正则可以判断是否符合身份证号码的基本要求,但是如果需要更加精确的校验的话,就需要通过编写一些方案来进行校验了。如各省级的地址码为:
华北:北京11,天津12,河北13,山西14,内蒙古15
东北: 辽宁21,吉林22,黑龙江23
华东: 上海31,江苏32,浙江33,安徽34,福建35,江西36,山东37
华中: 河南41,湖北42,湖南43
华南: 广东44,广西45,海南46
西南: 四川51,贵州52,云南53,西藏54,重庆50
西北: 陕西61,甘肃62,青海63,宁夏64,新疆65
特别:台湾71,香港81,澳门82
有些月份没有31号,校验码是否正确等等.....
欢迎访问我的个人网站(相信你会喜欢上我的风格):www.dengzhanyong.com
关注我的个人公众号【前端筱园】,不错过我的每一篇推送
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。