探究javascript中的正则表达式

正则


小编自己也在学习正则,分享给大家一起学习,本篇文章适用于对正则还不是了解的初级选手,废(xia)话(che)不(dan)多说,直接进入正题。

正则表达式定义

我们知道在js中正则表达式用RegExp对象表示,可以使用RegExp()构造函数来创建RegExp对象,但是在更多的情况下我们是用直接量的语法来创建。
栗子:

//使用直接量的形式
var  reg = /\w$/;
//使用构造函数的形式
var reg = new RegExp('\w$');

但我们如果有动态生成一个正则表达式的话,就需要我们用构造函数的形式了,我们又举个栗子:

var fmt = 'AA-bb cc:dd:ee';
var date = new Date();
var fmtObj = {
'A+': data.getMonth() + 1,
'b+': date.getDate(),
'c+': dategetHours(),
'd+': date.getMinutes(),
'e+': date.getSeconds(),
}
for(let key in fmtObj) {
 if(new RegExp(`(${key})`).test(fmt)){
fmt = fmt.repalce(RegExp.$1, RegExp.$1.lengt === 1 ? fmtObj[key] : ('00' + fmtObj[key]).substr((fmtObj[key] + '').length))
 }
}

那么什么是直接量字符呢?

直接量字符

正则表达式中所有的字母和数字都是按照字面量的含义进行匹配的,js的正则表达式也支持非字母的字符匹配,这些字符需要通过反斜杠\进行转译。下列表格中列举一些

名称 内容
\o NUL字符(\u0000)
\t 制表符(\u0009)
\n 换行符 (\u000A)
\v 垂直制表符(\u000B)
\f 换页符(\u000C)
\r 回车符(\u000D)
\xnn 由16进制数nn指定的拉丁字符,例如,\x0A等价于\n
\uxxxx 由16进制数xxxx指定的unicode字符,例如\u0009等价于\t
\cX 控制字符^X,\cJ等价于换行符\n
字母和数字字符 匹配自身

特殊含义的符号和字符

在正则表达式中,许多标点符号具有特殊的含义,他么是:^ $ . * + ? = ! : | / \ ( ) { } [ ];

某些符号只能在正则表达式的某些上下文中才会具有某种的特殊意义,在其他的上下文当中则会被直接当成直接量处理。如果想要在正则表达式中使用这些符号的直接量进行匹配,则必须使用前缀\, 这是一条通用的规则,其他标点符号(例如@和引号)没有特殊含义,在正则表达式中按照字面量含义进行匹配,如果不记得哪些标点符号需要反斜杠转义,可以在每个标点符号前面都加上转义符\。另外需要注意,许多字母和数字在有反斜杠做前缀时会有特殊含义,所以对于想按照直接量进行匹配的数字和字母,尽量不要用\进行转义

字符类

将直接量字符单独放进中括号内就组成了字符类。一个字符类可以匹配它所包含的任意字符,因此表达式/[abc]/就和字母abc中的任意一个都匹配

var reg = /[a-z0-9]/;

上面的正则表达式可以匹配任意一个字母或数字,其中a-z表示a-z的所有字母,0-9表示0到9之间的数字

反字符类[^...]

就是匹配字符类中不含有的字符,用^符号开头

var reg = /[^a-z0-9]/;

上面的正则表达式的意思就是匹配任意一个不含有字母和数字的字符

正则表达式的字符类

符号 内容
[...] 字符类
[^...] 反字符类
. 除换行符和其他unicode行终止符之外的任意字符
\w 数字,字母和下划线, 等价于[a-z0-9_]
\W 非数字,字母和下划线, 等价于[^a-z0-9_]
\s 任何unicode 空白符
\S 非任何unicode 空白符
\d 数字
\D 非数字
\b 单词边界
\B 非单词边界

正则表达式的重复语法

符号 内容
{n, m} 最少匹配n次,最多匹配m次
{n, } 至少匹配n次或更多
{n} 匹配n次
? 匹配0次或一次
+ 最少匹配一次
* 匹配0次或多次

非贪婪模式

上面我们列举出了匹配重复字符是尽可能多的匹配,而且允许后续的正则表达式继续匹配。因此我们称之为'贪婪'匹配模式。我们同样可以使用的正则表达式进行非贪婪匹配。只需要在待匹配的字符后跟随一个问号即可:'??', '+?', '*?' 或者'{1, 5}?'

 var a = /[a-z0-9]+?/
  a.exec('sdfff900') // ["s", index: 0, input: "sdfff900", groups: undefined]
  
  // 使用非贪婪模式匹配得到的结果可能和期望并不一样,可以来看看看下面的这个例子:
  var b = /a+?b/;
  b.exec('aaab') // ["aaab", index: 0, input: "aaab", groups: undefined]


  var c = /shen(ab)+?/;
  b.exec('shenababab'); // ["shenab", "ab", index: 0, input: "shenababab", groups: undefined]

这是因为正则表达式的模式匹配总是会寻找字符串中第一个可能匹配的位置,由于这个匹配是从字符串的第一个字符开始的,因此在这里不考虑他的子串中更短的匹配

选择,分组和引用

正则表达式语法还包括指定选择项,子表达分组和引用前一子表达式的特殊字符,我们分别来看一下
选择项: |用于分隔供选择的字符。
/ab|cd|ef/,可以匹配串ab,也可以匹配cd或者是ef。
注意选择项的尝试匹配的次序是从左到右,直到发现了匹配的项。如果左边的选择项匹配,就忽略右边的选择项,即使产生更好的匹配。所以,当正则表达式/a|ab/匹配字符串ab时,他只能匹配第一个字符串a

子表达式:

正则表达式中的圆括号有多种作用。

把单独的项组合成子表达式,以便可以像处理单独的单元那样用 | * + ? 等来对单元内的项进行处理。
var a = /java(script)?/; // 可以匹配字符串 'java',其后可以有'script'也可以没有。
  var b = /(ab|cd)+|ef/; // 可以匹配字符串ef, 也可以匹配ab或者cd的一次或多次重复
在完整的模式中定义子模式,当一个正则表达式和目标字符串成功匹配时,可以从目标字符串中拿到和圆括号中的字符模式相匹配的部分。
var a = /[a-z]+(\d+)/; // 那么这个时候我们可以从检索到的匹配中拿到和圆括号中子模式相匹配的数字

()表示捕获分组,()会把每个分组里的匹配的值保存起来,使用$n(n是一个数字,表示第n个捕获组的内容)

var reg = /\b(shen(sxx))/;
  reg.test('shensxx');
  console.log(RegExp.$1, RegExp.$2) // shensxx sxx
  
  reg.exec('shensxx');
  // ["shensxx", "shensxx", "sxx", index: 0, input: "shensxx", groups: undefined]

(?:)表示非捕获分组,和捕获分组唯一的区别在于,非捕获分组匹配的值不会保存起来。

var reg = /\b(shen(?:sxx))/;
  reg.test('shensxx');
  console.log(RegExp.$1, RegExp.$2) // shensxx
  
  reg.exec('shensxx');
  // ["shensxx", "shensxx", index: 0, input: "shensxx", groups: undefined]

引用:在同一正则表达式的后部引用前面的子表达式
这是通过在字符\后面加一位数字或多位数字来实现的,这个数字指定了圆括号的子表达式在正则表达式中的位置。\1,\3分别表示正则表达式中的第一个圆括号的子表达式和第三个子表达式(注意这里指的是自表达式中匹配的文本的引用,并不是指子表达式)
注意: 因为子表达式可以嵌套另一个子表达式,所以他们的位置是参与技术的左括号的位置

var a = /([Jj]ava([Ss]cript)?)\sis\s(fun\w*)/; // \2 则是代表的([Ss]sript)

    var b = /(['"])[^'"]*\1/; // 只能匹配单引号或者双引号是成对出现的,不允许出现一个单引号一个双引号

指定匹配位置

有一些正则表达式匹配的是字符串之间的位置,而不是实际的字符。例如\b匹配一个单词边界,即位于\w\W之间的边界,或者是一个字符串开始或结束的位置。像\b这样放任元素不匹配某个可见的字符,他们指定匹配发生的合法位置。还有一些锚元素^ $, 分表表示匹配字符串开始和结束的位置。
\b 匹配一个单词边界,即位于 \w 和 \W 之间的边界,或者是一个字符串开始或结束的位置。

var reg = /xx\b/; 
  reg.test('sxx shen') // true
  reg.test('xxs shen') // false
      
  var reg = /\bsx/;
  reg.test('sxx shen') // true
  reg.test('xsx shen') // false

  var reg = /\b\d+/;
  reg.test('.123') // true 
  reg.test('sxx124') // false

\B 匹配非单词边界。er\B 能匹配 verb 中的 er,但不能匹配 never中的 er
x(?=y) :正向先行断言,匹配 x 仅仅当 x 后面不跟着 y
x(?!y) :负向先行断言, 匹配 x 仅仅当x 后面不跟着 y

我们看下面的正则表达式,意思就是非单词边界后面跟这三个数字字符串,而且三个数字字符串后面不再跟数字

 var reg = /\B(?=(\d{3})+(?!\d))/g;
  '123456789.123456'.replace(reg, ',') // "123,456,789.123,456"

(?<=y)x :正向后行断言,匹配 x 仅仅当x 前面跟着 y

var reg = /(?<=95|98|NT|2000)Windows/;
  
  '3.1Windows'.replace(reg, 'aaaa'); // '3.1Windows'没有匹配到
  
  '2000Windows'.replace(reg, 'aaaa'); // '2000aaaa'

(?:负向后行断言,匹配 x 仅仅当 x 前面跟的不是 y

var reg = /(?

看个demo,不在小数点后加千分符

  var reg = /(?
修饰符

i: 执行时不区分大小写
g: 执行一个全局匹配,简而言之,就是找到所有的匹配,而不是在找到第一个之后就停止
m: 多行匹配模式,^匹配一行的开头和字符串的开头,$ 匹配行的结束和字符串的结束
使用字面量形式时: /\bjavascript\b/ig
构造函数形式: new RegExp('\bjavascript\b', 'ig')
用于模式匹配的String方法,string支持4种使用正则表达式的方式
search() 不支持全局匹配,会忽略修饰符g


@params: 一个正则表达式,如果参数不是一个正则表达式,那么会通过 `RegExp` 构造函数转成正则表达式
@return: 第一个与之匹配的字符串的起始位置,若是没有发生匹配就返回数字 `-1`
  'fsfsdjavsssjavasdfsfsjfjhhshh3r98u'.search(a) // 11
 
  var a = /[Jj]ava([Ss]crit)*/g;
  '012345javascript 67890'.search(a) // 6

replace()

@params: RegExp | string (正则表达式,可以设置修饰符)
@params: function | string
@return: 返回一个新的字符串,不改变源对象

如果第一个参数是一个string,那么 replace() 将直接搜索这个字符串然后进行替换(注意这里指替换第一个搜索到的结果),并不会先转成 RegExp 进行匹配。
如果第一个参数是一个 RegExp,那么 replace()将会进行正则匹配,将第一次匹配到结果进行替换,这里正则表达式可以设置修饰符。g 会进行全局多次匹配,将所有匹配到的结果进行替换
如果第二个参数是一个 string,那么 replace() 将匹配的结果直接用这个字符串替换
如果第二个参数是一个 function,我们看看可以有哪些参数:
@params: 完整模式匹配到的结果
@params: 完整模式下的子模式匹配到的结果,看正则表达式中有多少个圆括号,那么这里就可以有多少个这样的参数,每个参数表示对应的子模式匹配的结果
@return: 使用 return 的返回值替换匹配到的值

var a = /([Jj]ava([Ss]cript))\sis\s(fun\w*)/g;
    var b = 'ffhfhjavascript is functionsdfffsff';
    
    b.replace(a, function(match) {
        // 打印的结果就是 javascript is functionsdfffsff 123
        console.log(match, 123);
        return '@@@@'
    });
    // ffhfh@@@@

    b.replace(a, function(match, v1, v2, v3) {
        // 打印的结果就是 javascript is functionsdfffsff 123
        console.log(match, 123);
        // javascript
        console.log(v1);
        // script
        console.log(v2);
        // functionsdfffsff
        console.log(v3);
    
        return '@@@@'
    });
    // ffhfh@@@@

match()

@params: 正则表达式 | string(通过 `RegExp` 的构造函数转化成正则表达式)
@return: 数组,

如果这个正则表达式没有设置修饰符 g,match() 就不会进行全局检索,只检索第一个匹配。在这种情况下:数组的第一个元素就是就是完整模式匹配的结果,其余的元素则是正则表达式中圆括号的子表达式匹配的结果(如果没有圆括号就不会有这些元素)。index 表示匹配的位置,input 表示目标字符串

 var a = /([Jj]ava([Ss]cript))\sis\s(fun\w*)/;
  var b = 'ffhfhjavascript is functionsdfffsff';
  
  b.match(a)
  // ["javascript is functionsdfffsff", "javascript", "script", "functionsdfffsff", index: 5, input: "ffhfhjavascript is functionsdfffsff", groups: undefined]

如果添加了修饰符g,那么返回的数组中就是多次全局模式匹配的结果

 var a = /123(sxx)456(hello)/g;
  var b ='123sxx456helloffffjfljfj123sxx456hellojjweww123456hello123sxx456hello';
  
  b.match(a)
  // ["123sxx456hello", "123sxx456hello", "123sxx456hello"]

RegExp的两种方法:exec() 和 test()

exec()

@params: string
@return: 数组 | null

如果匹配到结果就返回一个数组,结果和字符串的 match() 方法的非全局匹配返回的结果一样。但是有一点和 match() 不一样,就是不管式全局还是非全局都是一样的结构。如果没有匹配到结果就返回 null

  var a = /123(sxx)456(hello)/g;
  var b ='123sxx456helloffffjfljfj123sxx456hellojjweww123456hello123sxx456hello';
  a.exec(b);
  // [“123sxx456hello", "sxx", "hello", index: 0, input: "123sxx456helloffffjfljfj123sxx456hellojjweww123456hello123sxx456hello", groups: undefined]

test()

@params: string
@return: boolean

这个方法很简单,匹配成功就返回true,否则就返回false


参考资料

JavaScript权威指南
https://juejin.im/post/5d0c5f47e51d4550bf1ae87b

文中如有错误的,望读者斧正

你可能感兴趣的:(探究javascript中的正则表达式)