欢迎光临
我们一直在努力

JavaScript | 正则表达式基础语法

文章目录

特点

JavaScript中的正则表达式不支持空白,必须写在一行中。

//匹配ab
console.log(/ab/.test('ab')); //true
console.log(/ ab/.test('ab')); //false
console.log(/a b/.test('ab')); //false
console.log(/ab /.test('ab')); //false

元字符

大部分字符在正则表达式中,就是字面的含义,比如/a/匹配a,/b/匹配b

/dog/.test("old dog") // true

但还有一些字符,除了字面意思外,还有特殊含义,这些字符被称为元字符,在JavaScript中共有14个元字符。

元字符         名称              匹配对象
.             点号               单个任意字符(除回车\r、换行\n、行分隔符\u2028和段分隔符\u2029外)
[]            字符组             列出的单个任意字符
[^]           排除型字符组        未列出的单个任意字符
?             问号               匹配0次或1次
*             星号               匹配0次或多次
+             加号               匹配1次或多次
{min,max}     区间量词            匹配至少min次,最多max次
^             脱字符             行的起始位置
$             美元符             行的结束位置
|             竖线               分隔两边的任意一个表达式
()            括号               限制多选结构的范围,标注量词作用的元素,为反向引用捕获文本
\1,\2...      反向引用            匹配之前的第一、第二...组括号内的表达式匹配的文本

转义字符

转义字符表示反斜杠 \ + 字符的形式,共有以下3中情况

1、因为元字符具有特殊含义,所以无法直接匹配,如果要匹配其自身,需要添加转义字符 \

console.log(/1+1/.test('1+1')); //false
console.log(/1\+1/.test('1+1')); //true
console.log(/\*/.test('*')); //true
console.log(/*/.test('*')); //报错

注意,并非所有元字符都需要专一,右方括号 ] 和 右花括号 } 不需要转义。

2、\ + 非元字符,用来打印一些不能打印的特殊字符

\0        NUL字符\u0000
[\b]      匹配退格符\u0008,不要与\b混淆
\t        制表符\u0009
\n        换行符\u000A
\v        垂直制表符\u000B
\f        换页符\u000C
\r        回车符\u000D
\xnn      由十六进制数nn指定的拉丁字符
\uxxxx    由十六进制数xxxx指定的Unicode字符(\u4e00-\u9fa5代表中文)  
\cX       控制字符^X,表示ctrl-[X],其中的X是A-Z之中任一个英文字母,用来匹配控制字符

3、 \ + 加任意其他字符,反斜杠会被忽略,仍然匹配原始内容

console.log(/\x/.test('x')); //true
console.log(/\y/.test('y')); //true
console.log(/\z/.test('z')); //true

双重转义

由于RegExp构造函数的参数是字符串,所以某些情况下,需要对字符进行双重转义,字符\在正则表达式字符串中通常被转义为\\

var p1 = /\.at/;
//等价于
var p2 = new RegExp('\\.at');
var p1 = /name\/age/;
//等价于
var p2 = new RegExp('name\\/age');
var p1 = /\w\\hello\\123/;
//等价于
var p2 = new RegExp('\\w\\\\hello\\\\123');

字符组

简单而言,就是指用方括号表示的一组字符,它匹配若干字符之一。

//匹配0-9这10个数字之一
var p = /[0123456789]/;
p.test('1');//true
p.test('a');//false

注意:字符组中的字符排列顺序并不影响字符组的功能,出现重复字符也不会影响

正则表达式通过连字符(-)提供了范围表示法,可以简化字符组

/[0123456789]/
//等价于
/[0-9]/
/[abcdefghijklmnopqrstuvwxyz]/
//等价于
/[a-z]/

连字符(-)表示的范围是根据ASCII编码的码值来确定的,码值小的在前,码值大的在后

所以[0-9]是合法的,而[9-0]会报错

在字符组中可以同时并列多个'-'范围

/[0-9a-zA-Z]/;//匹配数字、大写字母和小写字母
/[0-9a-fA-F]/;//匹配数字,大、小写形式的a-f,用来验证十六进制字符

console.log(/[0-9a-fA-F]/.test('d'));//true
console.log(/[0-9a-fA-F]/.test('x'));//false

只有在字符组内部,连字符'-'才是元字符,表示一个范围,否则它就只能匹配普通的连字符号

注意:如果连字符出现在字符组的开头或末尾,它表示的也是普通的连字符号,而不是一个范围

//匹配中划线
console.log(/-/.test('-'));//true
console.log(/[-]/.test('-'));//true

//匹配0-9的数字或中划线
console.log(/[0-9]/.test('-'));//false
console.log(/[0-9-]/.test('-'));//true
console.log(/[0-9-]/.test('-'));//true
console.log(/[-0-9]/.test('-'));//true
console.log(/[-0-9]/.test('-'));//true

排除

字符组的另一个类型是排除型字符组,在左方括号后紧跟一个脱字符'^'表示,表示在当前位置匹配一个没有列出的字符。

[^0-9]表示0-9以外的字符

//匹配第一个是数字字符,第二个不是数字字符的字符串
console.log(/[0-9][^0-9]/.test('1e'));//true
console.log(/[0-9][^0-9]/.test('q2'));//false

注意:在字符组内部,脱字符'^'表示排除,而在字符组外部,脱字符'^'表示一个行锚点

^符号是元字符,在字符组中只要^符号不挨着左方括号就可以表示其本身含义,不转义也可以

//匹配abc和^符号
console.log(/[a-c^]/.test('^'));//true
console.log(/[a-c\^]/.test('^'));//true
console.log(/[\^a-c]/.test('^'));//true

在字符组中,只有^、-、[、]这4个字符可能被当做元字符,其他有元字符功能的字符都只表示其本身

console.log(/[[1]]/.test('['));//false
console.log(/[[1]]/.test(']'));//false
console.log(/[\1]/.test('\\'));//false
console.log(/[^^]/.test('^'));//false
console.log(/[1-2]/.test('-'));//false
console.log(/[\[1\]]/.test('['));//true
console.log(/[\[1\]]/.test(']'));//true
console.log(/[\\]/.test('\\'));//true
console.log(/[^]/.test('^'));//true
console.log(/[1-2\-]/.test('-'));//true
console.log(/[(1)]/.test('('));//true
console.log(/[(1)]/.test(')'));//true
console.log(/[{1}]/.test('{'));//true
console.log(/[{1}]/.test('}'));//true
console.log(/[1$]/.test('$'));//true
console.log(/[1|2]/.test('|'));//true
console.log(/[1?]/.test('?'));//true
console.log(/[1*]/.test('*'));//true
console.log(/[1+]/.test('+'));//true
console.log(/[1.]/.test('.'));//true

简记

用[0-9]、[a-z]等字符组,可以很方便地表示数字字符和小写字母字符。对于这类常用字符组,正则表达式提供了更简单的记法,这就是字符组简记(shorthands)

常见的字符组简记有\d、\w、\s。其中d表示(digit)数字,w表示(word)单词,s表示(space)空白

正则表达式也提供了对应排除型字符组的简记法:\D、\W、\S。字母完全相同,只是改为大写,这些简记法匹配的字符互补

\d     数字,等同于[0-9]
\D     非数字,等同于[^0-9]
\s     空白字符,等同于[\f\n\r\t\u000B\u0020\u00A0\u2028\u2029]
\S     非空白字符,等同于[^\f\n\r\t\u000B\u0020\u00A0\u2028\u2029]
\w     字母、数字、下划线,等同于[0-9A-Za-z_](汉字不属于\w)
\W     非字母、数字、下划线,等同于[^0-9A-Za-z_]

注意:\w不仅包括字母、数字,还包括下划线。在进行数字验证时,只允许输入字母和数字时,不可以使用\w,而应该使用[0-9a-zA-Z]

任意字符

人们一般认为点号可以代表任意字符,其实并不是,.点号代表除回车(\r)、换行(\n) 、行分隔符(\u2028)和段分隔符(\u2029)以外的任意字符。

妥善的利用互补属性,可以得到一些巧妙的效果。比如,[\s\S]、[\w\W]、[\d\D]都可以表示任意字符

//匹配任意字符
console.log(/./.test('\r'));//false
console.log(/[\s\S]/.test('\r'));//true

量词

根据字符组的介绍,可以用字符组[0-9]或\d来匹配单个数字字符,如果用正则表达式表示更复杂的字符串,则不太方便

//表示邮政编码6位数字
/[0-9][0-9][0-9][0-9][0-9][0-9]/;
//等价于
/\d\d\d\d\d\d/;

正则表达式提供了量词,用来设定某个模式出现的次数

{n}       匹配n次
{n,m}     匹配至少n次,最多m次
{n,}      匹配至少n次
?         相当于{0,1}
*         相当于{0,}
+         相当于{1,}
//表示邮政编码6位数字
/\d{6}/;

协议名有http和https两种

/https?/;

量词广泛应用于解析HTML代码。HTML是一种标签语言,它包含各种各样的标签,比如<head>、<img>、<table>。它们都是从<开始,到>结束,而标签名的长度不同

console.log(/<[^<>]+>/.test('<head>'));//true
console.log(/<[^<>]+>/.test('<img>'));//true
console.log(/<[^<>]+>/.test('<>'));//false

HTML标签不能为空标签,而引号字符串的两个引号之间可以为0个字符

console.log(/'[^']*'/.test("''"));//true
console.log(/'[^']*'/.test("'abc'"));//true

贪婪模式

默认情况下,量词都是贪婪模式(greedy quantifier),即匹配到下一个字符不满足匹配规则为止

//exec()方法以数组的形式返回匹配结果
/a+/.exec('aaa'); // ['aaa']

懒惰模式

懒惰模式(lazy quantifier)和贪婪模式相对应,在量词后加问号'?'表示,表示尽可能少的匹配,一旦条件满足就再不往下匹配

{n}?       匹配n次
{n,m}?     匹配至少n次,最多m次
{n,}?      匹配至少n次
??         相当于{0,1}
*?         相当于{0,}
+?         相当于{1,}
/a+?/.exec('aaa'); // ['a']

匹配<script></script>之间的代码看上去很容易

/<script>[\s\S]*<\/script>/
//["<script>alert("1");</script>"]
/<script>[\s\S]*<\/script>/.exec('<script>alert("1");</script>'); 

但如果多次出现script标签,就会出问题

//["<script>alert("1");</script><br><script>alert("2");</script>"]
/<script>[\s\S]*<\/script>/.exec('<script>alert("1");</script><br><script>alert("2");</script>');     

它把无用的<br>标签也匹配出来了,此时就需要使用懒惰模式

//["<script>alert("1");</script>"]
/<script>[\s\S]*?<\/script>/.exec('<script>alert("1");</script><br><script>alert("2");</script>');     

在javascript中,/* */是注释的一种形式,在文档中可能出现多次,这时就必须使用懒惰模式

/\/\*[\s\S]*?\*\//
//["/*abc*/"]
/\/\*[\s\S]*?\*\//.exec('/*abc*/<br>/*123*/');

括号

括号有两个功能,分别是分组和引用。具体而言,用于限定量词或选择项的作用范围,也可以用于捕获文本并进行引用或反向引用

分组

量词控制之前元素的出现次数,而这个元素可能是一个字符,也可能是一个字符组,也可以是一个表达式

如果把一个表达式用括号包围起来,这个元素就是括号里的表达式,被称为子表达式

如果希望字符串'ab'重复出现2次,应该写为(ab){2},而如果写为ab{2},则{2}只限定b

console.log(/(ab){2}/.test('abab'));//true
console.log(/(ab){2}/.test('abb'));//false
console.log(/ab{2}/.test('abab'));//false
console.log(/ab{2}/.test('abb'));//true

身份证长度有15位和18位两种,如果只匹配长度,可能会想当然地写成\d{15,18},实际上这是错误的,因为它包括15、16、17、18这四种长度。因此,正确的写法应该是

/\d{15}(\d{3})?/

email地址以@分隔成两段,之前的部分是用户名,之后的部分是主机名,用户名允许出现数字、字母和下划线,长度一般在1-64个字符之间,则正则可表示为/\w{1,64}/

主机名一般表现为a.b.···.c,其中c为主域名,其他为级数不定的子域名,则正则可表示为/([-a-zA-z0-9]{1,63}.)+[-a-zA-Z0-9]{1,63}/

所以email地址的正则表达式如下:

var p =/\w{1,64}@([-a-zA-z0-9]{1,63}\.)+[-a-zA-Z0-9]{1,63}/;
console.log(p.test('q@qq.com'));//true
console.log(p.test('q@qq'));//false
console.log(p.test('q@a.qq.com'));//true

捕获

括号不仅可以对元素进行分组,还会保存每个分组匹配的文本,等到匹配完成后,引用捕获的内容。因为捕获了文本,这种功能叫捕获分组,比如,要匹配诸如2016-06-23这样的日期字符串。

/(\d{4})-(\d{2})-(\d{2})/

与以往不同的是,年、月、日这三个数值被括号括起来了,从左到右为第1个括号、第2个括号和第3个括号,分别代表第1、2、3个捕获组,javascript有9个用于存储捕获组的构造函数属性

RegExp.$1\RegExp.$2\RegExp.$3……到RegExp.$9分别用于存储第一、第二……第九个匹配的捕获组。在调用exec()或test()方法时,这些属性会被自动填充
console.log(/(\d{4})-(\d{2})-(\d{2})/.test('2016-06-23'));//true
console.log(RegExp.$1);//'2016'
console.log(RegExp.$2);//'06'
console.log(RegExp.$3);//'23'
console.log(RegExp.$4);//''

而exec()方法是专门为捕获组而设计的,返回的数组中,第一项是与整个模式匹配的字符串,其他项是与模式中的捕获组匹配的字符串

console.log(/(\d{4})-(\d{2})-(\d{2})/.exec('2016-06-23'));//["2016-06-23", "2016", "06", "23"]

捕获分组捕获的文本,不仅可以用于数据提取,也可以用于替换,replace()方法就是用于进行数据替换的,该方法接收两个参数,第一个参数为待查找的内容,而第二个参数为替换的内容

console.log('2000-01-01'.replace(/-/g,'.'));//2000.01.01

在replace()方法中也可以引用分组,形式是$num,num是对应分组的编号

//把2000-01-01的形式变成01-01-2000的形式
console.log('2000-01-01'.replace(/(\d{4})-(\d{2})-(\d{2})/g,'$3-$2-$1'));//'01-01-2000'

反向引用

英文中不少单词都有重叠出现的字母,如shoot或beep。若想检查某个单词是否包含重叠出现的字母,则需要引入反向引用(back-reference),反向引用允许在正则表达式内部引用之前捕获分组匹配的文本,形式是\num,num表示所引用分组的编号

//重复字母
/([a-z])\1/
console.log(/([a-z])\1/.test('aa'));//true
console.log(/([a-z])\1/.test('ab'));//false

反向引用可以用于建立前后联系。HTML标签的开始标签和结束标签是对应的

//开始标签
<([^>]+)>
//标签内容
[\s\S]*?
//匹配成对的标签
/<([^>]+)>[\s\S]*?<\/\1>/
console.log(/<([^>]+)>[\s\S]*?<\/\1>/.test('<a>123</a>'));//true
console.log(/<([^>]+)>[\s\S]*?<\/\1>/.test('<a>123</b>'));//false

非捕获

除了捕获分组,正则表达式还提供了非捕获分组(non-capturing group),以(?:)的形式表示,它只用于限定作用范围,而不捕获任何文本,比如,要匹配abcabc这个字符,一般地,可以写为(abc){2},但由于并不需要捕获文本,只是限定了量词的作用范围,所以应该写为(?:abc){2}

console.log(/(abc){2}/.test('abcabc'));//true
console.log(/(?:abc){2}/.test('abcabc'));//true

由于非捕获分组不捕获文本,对应地,也就没有捕获组编号

console.log(/(abc){2}/.test('abcabc'));//true
console.log(RegExp.$1);//'abc'
console.log(/(?:abc){2}/.test('abcabc'));//true
console.log(RegExp.$1);//''

非捕获分组也不可以使用反向引用

/(?:123)\1/.test('123123');//false
/(123)\1/.test('123123');//true

捕获分组和非捕获分组可以在一个正则表达式中同时出现

console.log(/(\d)(\d)(?:\d)(\d)(\d)/.exec('12345'));//["12345", "1", "2", "4", "5"]

选择

竖线'|'在正则表达式中表示或(OR)关系的选择,以竖线'|'分隔开的多个子表达式也叫选择分支或选择项。在一个选择结构中,选择分支的数目没有限制,在选择结构中,竖线|用来分隔选择项,而括号()用来规定整个选择结构的范围。如果没有出现括号,则将整个表达式视为一个选择结构,选择项的尝试匹配次序是从左到右,直到发现了匹配项,如果某个选择项匹配就忽略右侧其他选择项,如果所有子选择项都不匹配,则整个选择结构匹配失败。

console.log(/12|23|34/.exec('1'));//null
console.log(/12|23|34/.exec('12'));//['12']
console.log(/12|23|34/.exec('23'));//['23']
console.log(/12|23|34/.exec('2334'));//['23']

ip地址一般由3个点号和4段数字组成,每段数字都在0-255之间

(00)?\d; //0-9
0?[1-9]\d;//10-99
1\d{2};//100-199
2[0-4]\d;//200-249
25[0-5];//250-255
//数字(0-255)
/(00)?\d|0?[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]/;
//ip地址
var ip = /^(((00)?\d|0?[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.){3}\2$/;
console.log(ip.test('1.1.1.1'));//true
console.log(ip.test('1.1.1'));//false
console.log(ip.test('256.1.1.1'));//false

类似地,时间匹配也需要分段处理

//月(1-12)
0?\d|1[0-2]
//日(1-31)
0?\d|[12]\d|3[01]
//小时(0-24)
0?\d|1\d|2[0-4]
//分钟(0-60)
0?\d|[1-5]\d|60

手机号一般是11位,前3位是号段,后8位一般没有限制。而且,在手机开头很可能有0或+86

//开头
(0|\+86)?
//前3位
13\d|14[579]|15[0-35-9]|17[0135-8]|18\d
//后8位
\d{8}
//手机号码
var phone = /(0|\+86)?(13\d|14[579]|15[0-35-9]|17[0135-8]|18\d)\d{8}/;
console.log(phone.test('13453250661'));//true
console.log(phone.test('1913250661'));//false
console.log(phone.test('1345325061'));//false

在选择结构中,应该尽量避免选择分支中存在重复匹配,因为这样会大大增加回溯的计算量

//不良的选择结构
a|[ab]
[0-9]|\w

断言

在正则表达式中,有些结构并不真正匹配文本,而只负责判断在某个位置左/右侧是否符合要求,这种结构被称为断言(assertion),也称为锚点(anchor),常见的断言有3种:单词边界、行开头结尾、环视

单词边界

在文本处理中可能会经常进行单词替换,比如把row替换成line。但是,如果直接替换,不仅所有单词row都被替换成line,单词内部的row也会被替换成line。要想解决这个问题,必须有办法确定单词row,而不是字符串row

为了解决这类问题,正则表达式提供了专用的单词边界(word boundary),记为\b,它匹配的是'单词边界'位置,而不是字符。\b匹配的是一边是单词字符\w,一边是非单词字符\W的位置

与\b对应的还有\B,表示非单词边界,但实际上\B很少使用

console.log(/\ban\b/.test('an apple'));//true
console.log(/\ban\b/.test('a an'));//true
console.log(/\ban\b/.test('an'));//true
console.log(/\ban\b/.test('and'));//false
console.log(/\ban\b/.test('ban'));//false

起始结束

常见的断言还有^和$,它们分别匹配字符串的开始位置和结束位置,所以可以用来判断整个字符串能否由表达式匹配

//匹配第一个单词
console.log(/^\w*/.exec('first word\nsecond word\nthird word'));//['first']
//匹配最后一个单词
console.log(/\w*$/.exec('first word\nsecond word\nthird word'));//['word']
console.log(/^a$/.test('a\n'));//false
console.log(/^a$/.test('a'));//true

^和$的常用功能是删除字符串首尾多余的空白,类似于字符串String对象的trim()方法

function fnTrim(str){
    return str.replace(/^\s+|\s+$/,'')
}  
console.log(fnTrim('      hello world   '));//'hello world'

环视

环视(look-around),可形象地解释为停在原地,四处张望。环视类似于单词边界,在它旁边的文本需要满足某种条件,而且本身不匹配任何字符

环视分为正序环视和逆序环视,而javascript只支持正序环视,相当于只支持向前看,不支持往回看

而正序环视又分为肯定正序环视和否定正序环视

肯定正序环视的记法是(?=n),表示前面必须是n才匹配;否定正序环视的记忆法是(?!n),表示前面必须不是n才匹配

console.log(/a(?=b)/.exec('abc'));//['a']
console.log(/a(?=b)/.exec('ac'));//null
console.log(/a(?!b)/.exec('abc'));//null
console.log(/a(?!b)/.exec('ac'));//['a']
console.log(/a(?=b)b/.exec('abc'));//['ab']

注意:环视虽然也用到括号,却与捕获型分组编号无关;但如果环视结构出现捕获型括号,则会影响分组

console.log(/ab(?=cd)/.exec('abcd'));['ab']
console.log(/ab(?=(cd))/.exec('abcd'));['ab','cd']

优先级

正则表达式千变万化,都是由之前介绍过的字符组、括号、量词等基本结构组合而成的

//从上到下,优先级逐渐降低
\                            转义符
() (?!) (?=) []              括号、字符组、环视
* + ? {n} {n,} {n,m}         量词
^ $                          起始结束位置
|                            选择

由于括号的用途之一就是为量词限定作用范围,所以优先级比量词高

console.log(/ab{2}/.test('abab'));//false
console.log(/(ab){2}/.test('abab'));//true

注意:选择符'|'的优先级最低,比起始和结束位置都要低

console.log(/^ab|cd$/.test('abc'));//true
console.log(/^(ab|cd)$/.test('abc'));//false

局限性

  尽管javascript中的正则表达式功能比较完备,但与其他语言相比,缺少某些特性

  下面列出了javascript正则表达式不支持的特性

  【1】POSIX字符组(只支持普通字符组和排除型字符组)

  【2】Unicode支持(只支持单个Unicode字符)

  【3】匹配字符串开始和结尾的\A和\Z锚(只支持^和$)

  【4】逆序环视(只支持顺序环视)

  【5】命名分组(只支持0-9编号的捕获组)

  【6】单行模式和注释模式(只支持m、i、g)

  【7】模式作用范围

  【8】纯文本模式

赞(0) 打赏
未经允许不得转载:散人研 » JavaScript | 正则表达式基础语法
分享到: 更多 (0)

评论 抢沙发

9 + 1 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏