类别: 正则表达式
博客: https://blog.youkuaiyun.com/qtfying
掘金: https://juejin.im/user/57739929c4c9710055376671
QQ: 2811132560
邮箱: qtfying@gamil.com
上面的这个正则表达是很简单,但是在谈这个问题之前呢,我还是想聊聊正则表达式,一来呢,增加文章的可读性,二呢,也能帮助读者循序渐进,更好的过渡和理解
正则基础
创建正则表达式的方法
在JavaScript中可以通过两种方式去构造正则表达式。
- 第一种就是将正则表达式包裹在含在两个正斜杠中
const regex = /cat/;
- 第二种就是使用RegExp 构造函数
const regex = new RegExp('cat');
使用方法
- 使用方法就很简单了,两者都是调用正则的test函数,将要检测的值【字符串】传入即可
regex.test('cat');
regex.test('persian-cat')
- 不过还有一种更为简单的使用方法,这是正则表达式最简单的类型。能够直接在字符串中找到匹配的类型。
/cat/.test('cat');
/cat/.test('persian-cat');
/cat/.test('woca tmd');
用特殊字符实现更为复杂的功能
任何字符 — .
它是由一个.
表示。用来匹配除了换行符以外的任何单个字符串
const regex = /.og/;
regex.test('fog'); // true
regex.test('dog'); // true
通配符是特殊字符之一。如果想要匹配的是一个点 . 字符该怎么办?
转义符 — \
反斜杠 \
用于将特殊字符的含义切换为普通字符。所以是可以在文本中搜索点 .
字符的,并且这个点不会被解释为特殊字符。
const regex = /dog./;
regex.test('dog.'); // true
regex.test('dog1'); // true
const regex = /dog\./;
regex1.test('dog.'); // true
regex.test('dog1'); // false
字符集 — []
用方括号[]
表示。这个模式用来匹配一个字符,该字符可能是括号中的任何字符。
/[dfl]og/.test('dog'); // true
/[dfl]og/.test('fog'); // true
/[dfl]og/.test('log'); // true
需要注意的是字符串内的特殊字符(比如.
)不再特殊,因此在这里不需要反斜杠\
。我们来看一下其他的一些字符:
/[A-z].test('abc')/; // true
/[A-z].test('Z')/; // true
请注意,如果用字符集去匹配字符,记得大写字母一定是靠前的。这意味着 /[a-Z]/ 会引发错误
const pattern = /[a-Z]/;
Uncaught SyntaxError: Invalid regular expression: /[a-Z]/: Range out of order in character class
既然能正向能匹配字符,如果我想反向来呢,比如我匹配字符串不包括含有df两个字符,怎么来处理呢,当然可以,用^
,这个家伙在[]
表示的就是取反的意思
/[^df]og/.test('dog'); // false
/[^df]og/.test('fog'); // false
/[^df]og/.test('log'); // true
如果你想匹配大小写都有的一个字符串,慎用[A-Za-z]
,此时最好用不区分大小写的标志i
来进行忽略处理
多次重复 — {}
匹配某个表达式出现的确切次数,我们可以用{}
来实现,我们来用一个例子来,假如我们匹配一个电话号码格式如: +xx xxx xxx xxx
:
function isPhoneNumber(number){
return /\+[0-9]{2} [0-9]{3} [0-9]{3} [0-9]{3}/.test(number);
}
isPhoneNumber('+12 123 123 123'); // true
isPhoneNumber('123212'); // false
请注意,我们在此处进行了一些自定义:
{x}
完全匹配 x 次出现{x,}
至少匹配 x 次{x, y}
至少匹配 x 次且不超过 y 次
零个或多个重复 — /.*/
带有星号 * 的表达式可以匹配 0 次或更多次。它实际上等效于 {0,}
这样我们可以轻松构造一个可以匹配任意数量字符的模式:/.*/
修饰符
修饰符 | 描述 |
---|---|
i | 执行对大小写不敏感的匹配 |
g | 执行全局匹配(查找所有匹配而非在找到第一个匹配后停止) |
m | 执行多行匹配 |
忽略大小写 — i
/dog/i.test('dog'); // true
new RegExp('dog', 'i').test('DoG');
全局匹配
var str = 'aa'
var reg1 = /a/;
str.match(reg1) // 结果为:["a", index: 0, input: "aa"]
var reg2 = /a/g;
str.match(reg2) // 结果为:["a", "a"]
console.log(reg2.lastIndex) // 0
alert(reg2.test(str)) // true
console.log(reg2.lastIndex) // 1
alert(reg2.test(str)); // true
console.log(reg2.lastIndex) // 2
alert(reg2.test(str)); // false
console.log(reg2.lastIndex) // 0
alert(reg2.test(str)); // true
从上面的例子我们可以看出,我们可以总结出几点:
- 正常情况下,正则匹配到第一项即暂停
- 如全局匹配,会一直匹配到最后一项,直到匹配不了为止
- 正则有个lastIndex属性,这个属性每test(验证)一次都会自动加1,直到匹配不到位置,重新复位,这个不可复位性或叫做不可重入性
- 每个test后,可以将regex.lastIndex = 0,手动复位
咱们再看一个应用场景啊
const lorem = 'I_want_to_speak_english';
lorem.replace('_', ' '); // 'I want_to_speak_english'
lorem.replace(/_/g, ' '); // 'I want to speak english'
replace函数自带iteration (迭代),将匹配到的所有对象,全部依次替换为第二个参数内容
多行模式 — m
本来想着在最后再说这个多行模式呢,为了完整性,还是放到修饰符这,正则的每个修饰符和表达符都是串在一起的,你中有我我中有你,很难将具体的一个点完全剖开,单独拎出来讲,既然换行,肯定就是匹配每行里都可以包含的元素,且看示例:
const pets = `
dog
cat
parrot and other birds
`;
/^dog$/m.test(pets); // true
/^cat$/m.test(pets); // true
/^parrot$/m.test(pets); // false
我们可以看到,它改变了插入符号和美元符号的含义。在多行模式下,它们代表一行的开头和结尾,而不是整个字符串。同时对于换行符\n
同样有效。
示例中parrot是在pets的第三行能匹配到,但是并不是以parrot结尾,故为false
捕获组 — ()
有人疑问了,为啥{}
代表多次重复,这个()
出来了,代表着捕获分组,通俗一点就是每个括号的内容就是一个捕获分组,比如(\d)\d, 这里的 “(\d)” 这就是一个捕获分组,有多少个()
就代表着有多少个分组,这个有什么意义呢,其实就是方便给我们
匹配到的对象进行编号,通过编号呢,同时呢还允许我们对其进行命名,这样我们还能很方便通过$1
, $2
等对捕获组进行一一对应的拿到匹配对象中具体的某个值,比如:
编号 | 命名 | 捕获组 | 匹配内容 |
---|---|---|---|
0 | (\d{4})-(\d{2}-(\d\d)) | 2008-12-31 | |
1 | (\d{4}) | 2008 | |
2 | (\d{2}-(\d\d)) | 12-31 | |
3 | (\d\d) | 31 |
如果我们对其进行命名,则稍微调整即可
编号 | 命名 | 捕获组 | 匹配内容 |
---|---|---|---|
0 | (?<year>\d{4})-(?<date>\d{2}-(?<day>\d\d)) | 2008-12-31 | |
1 | year | (?<year>\d{4}) | 2008 |
2 | date | (?<date>\d{2}-(\d\d)) | 12-31 |
3 | day | (?<day>\d\d) | 31 |
也可以进行局部的命名,即上面上种方法混合,变成这样
编号 | 命名 | 捕获组 | 匹配内容 |
---|---|---|---|
0 | (\d{4})-(?<date>\d{2}-(\d\d)) | 2008-12-31 | |
1 | (\d{4}) | 2008 | |
2 | date | (?<date>\d{2}-(\d\d)) | 12-31 |
3 | (\d\d) | 31 |
回头看/(\w+)\s(\w+)/
现在咱们再来看这个/(\w+)\s(\w+)/
已经很好理解了,首先呢拆分每个符号具体的意义:
修饰符 | 描述 |
---|---|
\w | 匹配字母、数字、下划线。等价于’[A-Za-z0-9_]’ |
\s | 匹配任何空白字符,包括空格、制表符、换页符等等 |
() | 捕获组 |
所以呢就一目了然,/(\w+)\s(\w+)/
匹配的是符合 数字字母 + 空白符 + 数字字母
这中类型的值,其中两个括号内的值是一样的,都是匹配 数字字母
,且有且只有两项
咱们看个例子啊:
var re = /(\w+)\s(\w+)/;
var str = "zara ali haha hehe";
var newstr1 = str.replace(re, "$2、$1、$3");
console.log(RegExp.$1)
console.log(RegExp.$2)
console.log(RegExp.$3)
console.log(newstr1);
大家可以看一下这四个结果是什么
zara
ali
ali、zara、$3 haha hehe
看到这个结果,有的人是不是很惊讶,第三个和第四是怎么一回事,其实很简单,表达是只有两个捕获组,分别是
$1
-> ‘zara’$2
-> ‘ali’
根本就没有第三个捕获组,所以$3
当然是空了
RegExp
的构造函数中调用replace,分别对匹配到的进行替换,将匹配的第一个捕获组替换成$2
,同理将匹配的第二个捕获组替换成$1
,并和原来的未匹配到的进行拼接,那不就是 ali、zara、 haha hehe
了吗,对!
确实是这样样子的,但是replace方法在将当$n
为空时,直接将该$n
赋值给空和原字符串进行拼接,所以就得到了期望的ali、zara、$3 haha hehe
,这就是正则表达是的奥妙之处。
生活难道不也是这样,处处充满着惊喜,就想井中的月,就像沙漠里的水,就像极地上空的流星…人就是喜欢这些未知,着迷这些未知,才孜孜不倦的去探索…
作者:琴亭夜雨,写于2019年12月30日下午