由/(\w+)\s(\w+)/ 而感,一篇道尽js中的正则表达式

3,929 阅读5分钟

类别: 正则表达式
博客: blog.csdn.net/qtfying
掘金: juejin.cn/user/430094…
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日下午