正则表达式是来匹配一个字符串的。"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

五、如何写出高效的正则表达式

  1. 误匹配

    对于 +*? 这几个符号需要根据实际场景选择合适的使用,不要把他们混淆

  2. 漏匹配

    如需要匹配18位的身份证号,如果这样写 \d{18} 就会出现漏匹配的情况,因为身份证的最后一位可能是 X ,可以这样改进:\d{17}(X|x|\d)

  3. 明确

    通常越简单的正则匹配到的结果就越多,还是拿身份证号来举例,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
关注我的个人公众号【前端筱园】,不错过我的每一篇推送

邓占勇
109 声望6 粉丝

个人网站:www.dengzhanyong.com