手记

正则表达式学习笔记(2)- 术语和操作符

2. 相关术语与操作符

2.1 精准匹配

如果一个字符不是特殊字符或操作符,则表示该字符必须在表达式中出现。例如,在/test/正则中,有4个术语,它们表示这些字符必须在一个字符串中出现,才能匹配该模式。

简单的理解:精准匹配可以理解为一对一匹配,即正则表达式中中的术语与字符串中的字符对应。

2.2 匹配一类字符

表示匹配的一类代表一组特定含义的字符。例如:[abc]就代表匹配"a","b","c"字符中的一个,而不是代表匹配一个特定的字符。具体有如下几种:

字符 匹配内容
[...] 方括号内的任意字符
[^...] 不在方括号内的任意字符
· 除换行符和其他Unicode行终止符之外的任意字符
\w 任意ASCII字符组成的单词,等价于[a-zA-Z0-9_]
\W 任意不是ASCII字符组成的单词,等价于[^a-zA-Z0-9_]
\s 任何Unicode空白符
\S 任何非Unicode空白符的字符,注意w和S不同
\d 任何ASCII数字,等价于[0-9]
\D 除了ASCII数字之外的任何字符,等价于[^0-9]
\b 单词边界
\B 非单词边界
\t 水平制表符
\v 垂直制表符
\f 换页符
\r 回车
\n 换行符
\cA:\cZ 控制符,例如:\cM匹配一个Control-M
\u0000:\uFFFF 十六进制Unicode码
\x00:\xFF 十六进制ASCII码
// [abc] 表示匹配abc其中的一个
var str = 'abc',
    pattern = /[abc]/;

console.log(str.match(pattern));    // [ 'a', index: 0, input: 'abc' ]

// [^abc] 表示不匹配abc字符中的任何一个
var str = 'abcd',
    pattern = /[^abc]/;

console.log(str.match(pattern));    // [ 'd', index: 3, input: 'abcd' ]

// · 匹配除换行以外的字符
var str = '<p>I am a cat.</p>\n<b>what about you?</b>',
    pattern = /.*/;

console.log(str.match(pattern));    // [ '<p>I am a cat.</p>', index: 0, input: '<p>I am a cat.</p>\n<b>what about you?</b>' ]

// \w 匹配包括下划线的任意单词字符
var str = 'Your are _ so cute #*& so | beatiful ',
    pattern = /\w+/g;

console.log(str.match(pattern));   // [ 'Your', 'are', '_', 'so', 'cute', 'so', 'beatiful' ]

// \W 匹配任意非单词字符
var str = 'Your are _ so cute #*& so | beatiful ',
    pattern = /\W+/g;

console.log(str.match(pattern));    // [ ' ', ' ', ' ', ' ', ' #*& ', ' | ', ' ' ]

// \s 匹配空白字符
var str = 'Your \r\n\f ful',
    pattern = /\s/g;

console.log(str.match(pattern));    // [ ' ', '\r', '\n', '\f', ' ' ]

// \S 匹配任意非空白字符
var str = 'Your \r\n\f ful',
    pattern = /\S/g;

console.log(str.match(pattern));    // [ 'Y', 'o', 'u', 'r', 'f', 'u', 'l' ]

// \d 匹配任意数字
var str = '123helloworld34javascript',
    pattern = /\d+/g;

console.log(str.match(pattern));    // [ '123', '34' ]

// \D 匹配任意非数字
var str = '123helloworld34javascript',
    pattern = /\D+/g;

console.log(str.match(pattern));    // [ 'helloworld', 'javascript' ]

// \b 匹配单词边界
var str = 'I never come back',
    pattern = /e\b/g;

if (pattern.test(str)) {
    console.log(RegExp['$&']);  // e
    console.log(RegExp['$`']);  // I never com
}

// \B 匹配非单词边界
var str = 'I never come back',
    pattern = /e\B/g;

if (pattern.test(str)) {
    console.log(RegExp['$&']);  // e
    console.log(RegExp['$`']);  // I n
}

// \n 匹配换行符
var str = 'hello \n world',
    pattern = /\n/;

console.log(str.match(pattern));  // [ '\n', index: 6, input: 'hello \n world' ]

// \u0000:\uFFFF 匹配十六进制Unicode字符
var str = 'Visit W3School. Hello World!',
    pattern = /\u0057/g;

console.log(str.match(pattern));    // [ 'W', 'W' ]

// \x00:\xFF 匹配十六进制ASCII字符
var str = 'Visit W3School. Hello World!',
    pattern = /\x57/g;

console.log(str.match(pattern));    // [ 'W', 'W' ]

2.3 转义

与其他语言中的正则表达式类似,模式中使用的所有元字符都必须转义。正则表达式中的元字符包括:

( [ { \ ^ $ | ? * + . } ] )

由于RegExp构造函数的模式参数是字符串,所以在某些情况下需要双重转义。例如:

字面量模式 等价的字符串
/\[abc\]/ "\\[abc\\]"
/\w{2, 3}/ "\\w{2, 3}"
var reg1 = /\[abc\]/,
    reg2 = new RegExp("\\[abc\\]"),
    str = '[abc]';

console.log(str.match(reg1));   // [ '[abc]', index: 0, input: '[abc]' ]
console.log(str.match(reg2));   // [ '[abc]', index: 0, input: '[abc]' ]
console.log(reg1.source);       // \[abc\]
console.log(reg2.source);       // \[abc\]

2.4 匹配开始和结尾(^,$)

  • 匹配一个字符串的开始使用符号(^),例如: /^java/表示匹配已"java"开头的字符串
  • 匹配一个字符串的结尾使用符号($),例如: /script$/表示匹配已"script"结尾的字符串
  • 如果一个正则表达式中即出现了(^)又出现了($),表示必须匹配整个候选字符串,例如:/^javaScript$/表示匹配整个"javaScript"字符串
var str = 'javaScript is fun',
    str2 = 'JavaScript',
    reg1 = /^java/,
    reg2 = /fun$/i,
    reg3 = /^javascript$/i;

console.log(reg1.test(str));    // true
console.log(reg2.test(str));    // true
console.log(reg3.test(str));    // false
console.log(reg3.test(str2));   // true

2.5 重复

2.5.1 贪婪重复

要连续匹配四个'a',我们可以使用/aaaa/来进行匹配,但如果匹配的不止是四个,而是十几个呢?想必是不方便的,在重复匹配的选项上,正则表达式提供了很多方式。

贪婪匹配 贪婪匹配表示尽可能多的匹配,例如/a+/表示至少匹配一个"a"字符,即表示有多少个"a"就会匹配多少个,例如:

var str = 'aaaaa',
    str1 = 'aaaaaaaaaa',
    reg = /a+/;

console.log(reg.exec(str));     // [ 'aaaaa', index: 0, input: 'aaaaa' ]
console.log(reg.exec(str1));    // [ 'aaaaaaaaaa', index: 0, input: 'aaaaaaaaaa' ]

在贪婪重复上,正则表达式主要有以下方式:

字符 含义
{x,y} 匹配至少x次,最多y次
{x,} 匹配至少x次
{x} 匹配x次
? 匹配0次或者1次, { 0, 1 }
+ 匹配至少1次, { 1, }
* 匹配0次或者多次, { 0, }
// {2,3} 匹配至少2次,最多3次
var str = '.abc.def.xyz.com',
    reg = /(.\w+){2,3}/;

console.log(str.match(reg));    // [ '.abc.def.xyz', '.xyz', index: 0, input: '.abc.def.xyz.com' ]

// {2,} 匹配至少2次
var str = '.abc.def.xyz.com',
    reg = /(.\w+){2,}/;

console.log(str.match(reg));    // [ '.abc.def.xyz.com', '.com', index: 0, input: '.abc.def.xyz.com' ]

// {2} 匹配2次
var str = '.abc.def.xyz.com',
    reg = /(.\w+){2}/;

console.log(str.match(reg));    // [ '.abc.def', '.def', index: 0, input: '.abc.def.xyz.com' ]

// ? 匹配0次或者1次
var str = '.abc//.def.xyz.com',
    reg = /(.\w+\/?)?/;

console.log(str.match(reg));    // [ '.abc/', '.abc/', index: 0, input: '.abc//.def.xyz.com' ]

// * 匹配0次或者多次
var str = '.abc//.def.xyz.com',
    reg = /(.\w+\/*)*/;

console.log(str.match(reg));    // [ '.abc//.def.xyz.com', '.com', index: 0, input: '.abc//.def.xyz.com' ]

// + 匹配1次或者多次
var str = '.abc//.def.xyz.com',
    reg = /(.\w+\/+)+/;

console.log(str.match(reg));    // [ '.abc//', '.abc//', index: 0, input: '.abc//.def.xyz.com' ]
2.5.2 非贪婪重复

非贪婪模式就是在贪婪模式后面加上一个"?",表示尽尽可能少的匹配,例如:

{x,y}?, {x,}?, {x}?, ??, +? *?

var str = 'aaaa',
    str1 = '<p>Hello</p><p>Javascript</p>',
    reg = /a+?/,
    reg2 = /(<p>[^<]*<\/p>)+/,
    reg3 = /(<p>[^<]*<\/p>)+?/;

console.log(reg.exec(str));     // [ 'a', index: 0, input: 'aaaa' ]
console.log(str1.match(reg2));  // [ '<p>Hello</p><p>Javascript</p>', '<p>Javascript</p>', index: 0, input: '<p>Hello</p><p>Javascript</p>' ]
console.log(str1.match(reg3));  // [ '<p>Hello</p>', '<p>Hello</p>', index: 0, input: '<p>Hello</p><p>Javascript</p>' ]

2.6 分组

使用()表示分组,表示匹配一类字符串,例如:/(\w\s)+/表示匹配一个或者多个以字母和空格组合出现的字符串

var str = 'a b c',
    reg = /(\w\s)+/;

console.log(str.match(reg));    // [ 'a b ', 'b ', index: 0, input: 'a b c' ]
  • 捕获组

当用于模式匹配之后,匹配到的元组就被成为捕获组。捕获组对应着括号的数量,分别使用$1,$2...$99...$x表示匹配到的捕获组,另外可以使用\1,\2...\99...\x表示引用捕获组。

var str = '20170809',
    str2 = '20170808',
    reg = /(\d{4})(\d{2})(\d{2})/,
    reg2 = /(\d{4})(\d{2})\2/;

console.log(str.replace(reg, '$1-$2-$3'));  // 2017-08-09
console.log(str.replace(/a/, '$1/$2'));     // 20170809
console.log(str.replace(reg2, '$1/$2'));    // 20170809
console.log(str2.replace(reg2, '$1/$2'));   // 2017/08

可以看到第三个打印与第四个打印之间的差异,原因是因为第三个没有正则匹配项。

\x表示引用,引用的是具体的匹配字符串,也就是说上面例子中的\2引用的是第二个捕获组中的内容,其实应该对应的是"08"字符串,因此"20170808"当然与"20170809"字符串不匹配;反证可以看第四个匹配,验证了上面的结果。

  • 非捕获组

若希望以()分组的元组在匹配的时候不被捕获,可以使用如下形式:

(?:str|pattern)

var str2 = '20170808',
    reg2 = /(?:\d{4})(\d{2})\1/;

console.log(str2.replace(reg2, '$1'));   // 08

2.7 或操作符(|)

用(|)表示或者的关系。例如:/a|b/表示匹配字符"a"或者"b",/(ab)+|(def)+/表示匹配一次或者多次出现的"ab"或者"def"

2.8 断言

正则表达式中的断言大体分为两类,先行断言与后发断言;在每一种断言中又分为正环顾和负环顾,因此一共有四种断言形式。

  • 先行断言

通俗的理解,先行断言就是表示在匹配的字符串后必须出现(正)或者不出现(负)什么字符串

(?=patten) 零宽正向先行断言

(?!patten) 零宽负向先行断言

// (?=patten)
var str = 'yuzhongzi91',
    str2 = '123abc',

    // 表示匹配以字母或者数字组成的,并且第一个字符必须为小写字母开头的字符串
    reg = /^(?=[a-z])([a-z0-9])+$/;

console.log(str.match(reg));    // [ 'yuzhongzi91', '1', index: 0, input: 'yuzhongzi91' ]
console.log(str2.match(reg));   // null

// (?!pattern)
var str = 'bb<div>I am a div</div><p>hello</p><i>world</i><p>javascript</p>',

    // 表示匹配除了<p>标签之外的标签
    // 分析:
    // ?! 表示否定,这个可以先不用看
    // \/?p\b 表示匹配"/p"或者"p"
    // [^>]+ 表示匹配不是">"字符
    // 如果没有"?!"就表示匹配"<p>"标签
    // 而加上了"?!",就表示否定,因此就是匹配除了<p>标签之外的标签
    reg = /<(?!\/?p\b)[^>]+>/g;

console.log(str.match(reg));    // [ '<div>', '</div>', '<i>', '</i>' ]
  • 后发断言(兼容性,测试目前仍不支持)

通俗的理解,后发断言就是表示在匹配的字符串前必须出现(正)或者不出现(负)什么字符串

(?<=patten) 零宽正向后发断言

(?<!patten) 零宽负向后发断言

var str = '<p>hello</p>',
    reg = /(?<=<p>).*(?=<\/p>)/;

console.log(str.match(reg));    // 兼容情况下会匹配到"hello"字符串
3人推荐
随时随地看视频
慕课网APP