一文让你彻底搞懂正则表达式,从此不再copy

正则表达式是来匹配一个字符串的。"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
(? 负向后发断言 匹配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"]

负向后发断言

形如 (? 的形式,表示匹配到A,且A前面的不满足B。

var str = 'I scream, you scream, we all scream for ice-cream!';
// 匹配cream,且前面不能为字母
str.match(/(?

可能很多人看了之后很容易把这几个记忆混淆,这里教大家一个简单的方法理解与记忆:

  • 断言(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
关注我的个人公众号【前端筱园】,不错过我的每一篇推送

你可能感兴趣的:(前端,正则表达式)