javascript 正则表达式

435 阅读7分钟

RegExp 对象

JavaScript 通过内置对象 RegExp 支持正则表达式;

有两种方法实例化 RegExp 对象:

  • 字面量
var reg = /\bis\b/g; // \b 就是单词边界
  • 构造函数
// RegExp 构造函数中的第一个参数是正则表达式文本,js中反斜线为特殊字符,因此要转义;第二个参数是标志。
var reg = new RegExp('\\bis\\b', 'g');

修饰符

  • g:global 全文搜索,不添加,搜索到第一个匹配停止
  • i:ignore case 忽略大小写,默认大小写敏感
  • m:multiple lines 多行搜索

元字符

  • 正则表达式由两种基本字符类型组成:

    • 原义文本字符:代表其本身含义的字符,比如 'abc' ,就是匹配 abc 这个字符
    • 元字符
  • 元字符是在正则表达式中有特殊含义的非字母字符:

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

    • 字符含义
      \t水平制表符
      \v垂直制表符
      \n换行符
      \r回车符
      \O空字符
      \f换页符
      \cX与X对应的控制字符(Ctrl + X)

字符类

​ 一般情况下正则表达式一个字符对应字符串一个字符;表达式ab\t的含义就是字符 'ab' 加一个水平制表符。但是现在不想匹配某个的字符,现在想要匹配某类的字符,这样的话就可以构造一个字符类:

  • 我们可以使用元字符 [] 来构建一个简单的类
  • 所谓类是指符合某些特性的对象,是一个泛指而不是特指某个字符。
  • 表达式[abc]把字符 a 或 b 或 c 归为一类,表达式可以匹配这类的字符(就是说有 a b c 之中的一个字符就行了)。

字符类取反

  • 使用元字符^创建 反向类/负向类
  • 反向类的意思是不属于某类的内容
  • 表达式 [^abc] 表示 不是字符 a 或 b 或 c 的内容

范围类

使用字符类匹配数字有时候就会非常麻烦,比如:[0123456789]

  • 正则表达式提供了范围类。
  • 所以我们可以用[a-z]来连接两个字符表示 从 a 到 z 的任意字符。
  • 这是个闭区间,也就是包含 a 和 z 本身。
  • [] 组成的类内部是可以连写的:[a-zA-Z]

那么如果在中括号中想要专门匹配 - 该如何呢?如果 - 写到了两个数字之间或者两个字母之间,那么它表示的就是范围的意思;因此可以写到最末尾,这样就代表一个横线的意思:[0-9-],表示的就是匹配 0~9 之间的数字和 -

JS 预定义类及边界

预定义类

正则表达式提供了预定义类来匹配常见的字符类。

字符等价类含义
.[^\r\n]除了回车符和换行符之外的所有字符
\d[0-9]数字字符
\D[^0-9]非数字字符
\s[\t\n\x0B\f\r]空白符
\S[^\t\n\x0B\f\r]非空白符
\w[a-zA-Z_0-9]单词字符(字母、数字、下划线)
\W[^a-zA-Z_0-9]非单词字符

**例子:**匹配一个 ab + 数字 + 任意字符 的字符串:ab\d.

边界

正则表达式还提供了几个常用的边界匹配字符:

字符含义
^以 xxx 开始
$以 xxx 结束
\b单词边界
\B非单词边界
'@123@abc@'.replace(/@./g, 'Q'); // 'Q23Qbc@'
'@123@abc@'.replace(/^@./g, 'Q'); // 'Q23@bc@'
'@123@abc@'.replace(/@.$/g, 'Q'); // '@23@bcQ'
// 注意 ^ 和 $ 的书写位置;^ 写到开头,$ 写到结尾
'this is a iasdfafs word'.replace(/\bis\b/, 0);
// "this 0 a iasdfafs word"
// 注意, 只有 is 被替换了,this 和 iasdfafs 都没被替换(一开始以为iasdfafs 会被替换,因为是以 i 和 s 作为边界的,发现并不是这样,因此要特别注意)

量词

我们希望匹配一个连续出现20次数字的字符串,不能连续写20个\d,这个时候就需要用到量词。

字符含义
?出现零次或一次(最多出现一次)
+出现一次或多次(至少出现一次)
*出现零次或多次(任意次)
{n}出现 n 次
{n, m}出现 n 到 m 次
{n,}至少出现 n 次

JS 正则贪婪模式与非贪婪模式

贪婪模式

\d{3,6}来匹配字符串 "12345678",它究竟会怎么匹配呢?这个时候正则表达式默认会尽可能多地匹配,直到匹配失败。

'12345678'.replace(/\d{3,6}/, 'X'); // "X78"

非贪婪模式

让正则表达式尽可能少的匹配,也就是说一旦成功匹配不再继续尝试就是非贪婪模式。做法就是在量词后面加上 ?

'12345678'.replace(/\d{3,6}?/, 'X'); // "X45678"

分组

场景:匹配字符串 "Kong" 连续出现 3 次的场景。如果使用Kong{3}这样的正则表达式,那么匹配到的是末尾的 g 连续出现 3 次的场景,比如 "konggg",而并不是匹配到整个单词。

'konggg'.replace(/kong{3}/g, 'X'); // "X"

使用 ()可以达到分组的功能,使量词作用于分组:(kong){3},这样就是匹配整个单词出现三次。

'kongkongkong'.replace(/(kong){3}/g, 'X'); // "X"

使用 | 可以达到 的效果。

'kongking'.replace(/k(on|in)g/g, 'X'); // "XX"

反向引用

分组了之后,用 $1$2这种形式来表示分组(没有括号的话$数字这种形式没用)

将 "年-月-日" 替换成 "月/日/年":2015-12-25 => 12/25/2015

'2015-12-25'.replace(/(\d{4})-(\d{2})-(\d{2})/, '$2/$3/$1');
// "12/25/2015"

忽略分组

不希望捕获某些分组,只需要在分组内加上 ?:就可以。

'kongling'.replace(/k(?:on)gl(ing)/, '$1'); // "ing"

前瞻

  • 正则表达式从文本头部向尾部开始解析,文本尾部方向,称为 "前"。
  • 前瞻就是正则表达式匹配到规则的时候,向前检查是否符合断言,后顾/后瞻方向相反
  • JavaScript 不支持后顾
  • 符合和不符合特定断言分别称为肯定/正向匹配和否定/负向匹配
名称正则含义
正向前瞻exp(?=assert)
负向前瞻exp(?!assert)
正向后顾exp(?<=assert)Javascript不支持
负向后顾exp(?<!assert)Javascript不支持
// \w(?=\d) 意思就是匹配到了单词字符了以后,往前走,看看是不是一个数字。
'a2*3'.replace(/\w(?=\d)/g, 'X'); // "X2*3"

JS 对象属性

  • glabel:是否全文搜索,默认 false
  • ignore case:是否大小写敏感,默认是 false
  • multiline:多行搜索,默认值使 false
  • lastIndex:是当前表达式匹配内容的最后一个字符的下一个位置
  • source:正则表达式的文本字符串

RegExp的test 和 exec 方法

RegExp.prototype.test(str)

  • 用于测试字符串参数中是否存在匹配正则表达式模式的字符串
  • 如果存在就返回 true
var reg = /\w/;
reg.test('a'); // true
reg.test('*'); // false

如果在正则表达式后面用了 g 那就要注意了,它每次匹配的时候并不是从头开始匹配的,而是从lastIndex开始的;

var reg = /\w/g;
reg.test('ab'); // true
reg.test('ab'); // true
reg.test('ab'); // false
// 发现了后面已经没有内容之后,lastIndex 又变成了0

RegExp.prototype.exec(str)

  • 使用正则表达式模式对字符串执行搜索,并将更新全局 RegExp 对象的属性以反应匹配结果。
  • 如果没有匹配的文本则返回null,否则返回一个结果数组,数组包含额外属性:
    • index: 声明匹配文本的第一个字符的位置
    • input: 存放被检索的字符串 string

非全局调用

  • 调用非全局 RegExp 对象的 exec()时,返回数组
  • 每次匹配lastIndex都不会变,因为都是从头开始匹配
  • 第一个元素是与正则表达式相匹配的文本
  • 第二个元素是与RegExpObject的第一个子表达式想匹配的文本(如果有的话)
  • 第三个元素是与RegExp对象得第二个子表达式想匹配的文本(如果有的话),以此类推
var reg = /\d(\w)\d/;
var str = '1a2b3c4d5e6f7g';
var result = reg.exec(str); // exec() 会返回一个数组,数组有属性 index 和 input
console.log(result.index); // 0;为匹配文本的第一个字符的位置
console.log(result.input); // 1a2b3c4d5e6f7g;为被检索的文本
console.log(result); // ["1a2", "a"];第一项为第一个匹配的文本,第二个为子表达式(其实就是正则表达式括号括起来的部分)

全局调用

  • 全局调用时,可以多次执行exec()来查找同一个字符串中的成功匹配。
  • 查找将从正则表达式的lastIndex属性指定的位置开始(要注意的是test()也会更新lastIndex的属性)。
  • **注意:**即使再次查找的字符串不是原查找字符串时,lastIndex也不会被重置,它依旧会从记录的lastIndex开始。
var reg = /\d(\w)\d/g;
var str = '1a2b3c4d5e6f7g';
while (result = reg.exec(str)) {
    console.log(reg.lastIndex);
    console.log(result.index); 
	console.log(result.input);
	console.log(result); 	
}
/*
	第一次循环:
        3
        0	
        1a2b3c4d5e6f7g
        ["1a2", "a"]
*/
/*
	第二次循环:
        7
        4	
        1a2b3c4d5e6f7g
        ["3c4", "c"]
*/
/*
	第三次循环:
        11
        8	
        1a2b3c4d5e6f7g
        ["5e6", "e"]
*/

字符串对象的方法

String.prototype.search(reg)

  • search()方法用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串
  • 方法返回第一个匹配结果index,查找不到返回 -1
  • search()方法不执行全局匹配,它将忽略标志 g ,并且总是从字符串的开始进行检索
console.log('a1b2c3d4'.search('2')); // 3
console.log('a1b2c3d4'.search('20')); // -1
console.log('a1b2c3d4'.search(2)); // 3;发现这里填数字也可以,这是因为这些字符串的方法,如果跟正则相关,那么当传入的参数不是正则的时候它会尽量将其转换成正则的形式,因此这个数字转换成正则的形式就是 /2/
console.log('a1b2c3d4'.search(/2/)); // 3
console.log('a1b2c3d4'.search(/2/g)); // 3
console.log('a1b2c3d4'.search(/2/g)); // 3;可见总是从字符串的开始进行检索

String.prototype.match(reg)

  • match()方法将检索字符串,以找到一个或多个与regexp匹配的文本。
  • regexp是否具有标志 g 对结果影响很大。

非全局调用

  • 如果regexp没有标志 g ,那么match() 方法就只能在字符串中执行一次匹配。
  • 如果没有找到任何匹配的文本,将返回 null
  • 否则它将返回一个数组,其中存放了与它找到的匹配文本有关的信息。
  • 返回数组的第一个元素存放的是匹配文本,而其余的元素存放的是与正则表达式的子表达式匹配的文本,
  • 除了常规的数组元素之外,返回的数组还含有 2 个对象属性:
    • **index:**声明匹配文本的起始字符在字符串的位置
    • **input:**声明对stringObject的引用

(感觉跟正则表达式的exec()方法的结果一毛一样)

var reg = /\d(\w)\d/;
var str = '1a2b3c4d5e';
var result = str.match(reg);
console.log(reg.lastIndex); // 0;如果匹配上了那么lastIndex不可能为0,那就说明这个操作不会动lastIndex
console.log(result.index); // 0
console.log(result.input); // 1a2b3c4d5e
console.log(result); // ["1a2", "a"]

全局调用

  • 如果regexp具有标志 g 则 match()方法将执行全局检索,找到字符串中的所有匹配子字符串
  • 没有找到任何匹配的子串,则返回 null
  • 如果找到了一个或多个匹配子串,则返回一个数组
  • 数组元素中存放的是字符串中所有的匹配子串,而且也没有index属性或input属性。
var reg = /\d(\w)\d/g;
var str = '1a2b3c4d5e';
var result = str.match(reg);
console.log(reg.lastIndex); // 0
console.log(result.index); // undefined
console.log(result.input); // undefined
console.log(result); // ["1a2", "3c4"]

String.prototype.split(reg)

  • 我们经常使用 split方法把字符串分割为字符数组:
'a,b,c,d'.split(','); // ['a', 'b', 'c', 'd']
  • 一些复杂的分割情况下我们可以使用正则表达式解决
'a1b2c3d'.split(/\d/); // ['a', 'b', 'c', 'd']

String.prototype.replace()

  • String.prototype.replace(str, replaceStr)
  • String.prototype.replace(reg, replaceStr)
  • String.prototype.replace(reg, function)
'a1b'.replace('1', 2); // "a2b"
'a1b1c1'.replace('1', 2); // "a2b1c1";发现只替换了第一个 1 .说明其并不是全局替换(可以这么理解:第一个参数它也将你转换成正则表达式的形式,因此就是 /1/ ,所以就不是全局的)
'a1b1c1'.replace(/1/g, 2); // "a2b2c2"

function 参数的意义

对于第二个参数为 function 的情况,其返回值为替换结果

function 会在每次匹配替换的时候调用,有四个参数(不固定):

1、匹配的子串

2、正则表达式分组内容,没有分组则没有该参数(如果有两个分组,那就有两个参数)

3、匹配项在字符串中的 index

4、原字符串

如果是全局的话,每匹配一次就会调用一次回调函数

'a1b2c3d4e5'.replace(/\d/g, function(match, index, origin) {
    console.log(match);
    console.log(index);
    return parseInt(match) + 1;
})
/*
	第一次循环:
		match:1
		index:1
*/
/*
	第二次循环:
		match:2
		index:3
*/
/*
	第三次循环:
		match:3
		index:5
*/
/*
	第四次循环:
		match:4
		index:7
*/
/*
	第五次循环:
		match:5
		index:9
*/

// 替换后的字符串:"a2b3c4d5e6"
'a1b2c3d4e5'.replace(/(\d)(\w)(\d)/g, function(match, group1, group2, group3, index, origin) {
    console.log(match);
    console.log(index);
    return group1 + group3;
})
/*
	第一次循环:
		match:1b2
		index:1
*/
/*
	第二次循环:
		match:3d4
		index:5
*/

// 替换后的字符串:"a12c34e5"