一篇文章搞懂 js 正则表达式

877 阅读5分钟

正则表达式是用于匹配字符串中字符组合的模式,在 JavaScript 中,正则表达式也是对象。这些模式被用于 RegExpexectest 方法, 以及 Stringmatchreplacesearchsplit 方法。

创建正则表达式

  1. 使用一个正则表达式字面量,其由包含在斜杠之间的模式组成,如下所示:
const regex = /ab+c/;
const regex = /^[a-zA-Z]+[0-9]*\W?_$/gi;
  1. 或者调用RegExp对象的构造函数,如下所示:
let regex = new RegExp("ab+c");

let regex = new RegExp(/^[a-zA-Z]+[0-9]*\W?_$/, "gi");

元字符

基本元字符

字符 含义
. 表示匹配除换行符以外的任意字符。例如 /.n/ 将会匹配 "nay, an apple is on the tree" 中的 'an''on',但是不会匹配 'nay'
[abc] 表示匹配方括号中的任意字符,比如 /a[123]b/ 可以匹配如下三种字符串:"a1b"、"a2b"、"a3b"
[^abc] 一个反向字符集。也就是说, 它匹配任何没有包含在方括号中的字符,你可以使用破折号(-)来指定一个字符范围。例如,[^abc][^a-c] 是一样的。
x | y 匹配‘x’或者‘y’。 优先级最低,例如,/green|red/ 匹配 “green apple” 中的 ‘green’“red apple” 中的 ‘red’
( ) 1. 提升优先级. 2. 分组
x(?=y) 匹配 'x' 仅仅当 'x' 后面跟着 'y'.这种叫做正向肯定查找。例如,/Jack(?=Sprat)/ 会匹配到 'Jack' 仅仅当它后面跟着 'Sprat'。但是 ‘Sprat’ 不是匹配结果的一部分。

如果字符组里的字符特别多的话,可以使用范围表示法:比如[123456abcdefGHIJKLM],可以写成 [1-6a-fG-M]。用连字符 - 来省略和简写。

限定元字符

字符 含义
{m,n} 表示连续出现最少m次,最多n次。
{m,} 表示至少出现m次。
{m} 等价于{m,m},表示出现m次。
? 等价于{0,1} 表示要么0个或1个
+ 等价于{1,},表示出现1次或多次
* 表示0个或多个

简写元字符

字符 含义
\d 匹配一个数字。等于[0-9],数字的英文 digit
\D 匹配一个非数字字符,等价于[^0-9]
\w 匹配一个单词字符,表示数字、字母、下划线,等价于[0-1a-zA-Z_],w是word的简写
\W 匹配一个非单词字符,等价于 [^0-9a-zA-Z_]
\s 表示空白字符,包括空格、制表符、换页符和换行符。等价于 [ \t\v\n\r\f] ,记忆方式:s是space character的首字母。例如, /\s\w*/ 匹配 "foo bar."中的 ' bar'
\S 表示非空白字符,例如 /\S\w*/ 匹配 "foo bar." 中的 'foo'
\b 是单词边界,具体就是 \w\W 之间的位置,也包括 \w^ 之间的位置,也包括 \w$ 之间的位置。
\f 匹配一个换页符 (U+000C)。
\n 匹配一个换行符 (U+000A)。
\r 匹配一个回车符 (U+000D)。
\t 匹配一个水平制表符 (U+0009)。
\v 匹配一个垂直制表符 (U+000B)。

一个文件名是 "[JS] Lesson_01.mp4" 中的 \b,如下

var result = "[JS] Lesson_01.mp4".replace(/\b/g, '#');
console.log(result); 
// => "[#JS#] #Lesson_01#.#mp4#"

位置元字符

字符 含义
^ 匹配输入的开始。例如,/^A/ 并不会匹配 "an A" 中的 'A',但是会匹配 "An E" 中的 'A'。
$ 匹配输入的结束。例如,/t$/ 并不会匹配 "eater" 中的 't',但是会匹配 "eat" 中的 't'。

标志字符

字符 含义
g 表示全局搜索,global
i 表示不区分大小写,ignoreCase
m 多行搜索,multiline

贪婪模式

贪婪模式是指在整个表达式匹配成功的前提下,尽可能多的匹配

var regex = /\d{2,5}/g;
var string = "123 1234 12345 123456";
console.log( string.match(regex) ); 
// => ["123", "1234", "12345", "12345"]

其中正则/\d{2,5}/,表示数字连续出现2到5次。会匹配2位、3位、4位、5位连续数字。

但是其是贪婪的,它会尽可能多的匹配。

var s = '1234567890';
var r = /(.+)(.+)(.+)/;
console.log( s.match(r) ); 
//  ["1234567890", "12345678", "9", "0"]

第一个圆括号里匹配到的是 12345678,第二个匹配到的是 9,第三个匹配到的是 0,从左到右,匹配个数的强度是由强到弱,但是只有两个级别,要么强要么弱,即第一个强,后面的几个一样弱。

有时贪婪不是一件好事,而惰性匹配,就是尽可能少的匹配:

var regex = /\d{2,5}?/g;
var string = "123 1234 12345 123456";
console.log( string.match(regex) ); 
// => ["12", "12", "34", "12", "34", "12", "34", "56"]

其中 /\d{2,5}?/ 表示,虽然2到5次都行,当2个就够的时候,就不在往下尝试了。通过在量词后面加个问号就能实现惰性匹配。

正则对象的方法

test

接受一个字符串参数,如果正则表达式与指定的字符串匹配返回 true 否则返回 false

let str = 'hello world!';
let result = /^hello/.test(str);
console.log(result); 
// true

exec

exec() 方法在一个指定字符串中执行一个搜索匹配。返回一个结果数组或 null

返回的数组将完全匹配成功的文本作为第一项,如果没有匹配到就返回 null。数组还包含两个属性,index 表示匹配文本在字符串中的位置,input 表示被解析的原始字符串。

当正则表达式使用 "g" 标志时,可以多次执行 exec 方法来查找同一个字符串中的成功匹配。当你这样做时,查找将从正则表达式的 lastIndex 属性指定的位置开始。(test() 也会更新 lastIndex 属性)。

var reg = /ab*/g;
var str = 'abbcdefabh';
var myArray;
while ((myArray = reg.exec(str)) !== null) {
  var msg = 'Found ' + myArray[0] + '. ';
  msg += 'Next match starts at ' + reg.lastIndex;
  console.log(msg);
}
// Found abb. Next match starts at 3
// Found ab. Next match starts at 9

如果正则表达式中有分组(有圆括号),分组都是从 1 开始的编号,第一项匹配到的结果就在数组的第 1 项,第 n 项匹配到的结果就在数组的第 n 项。

var s = '1234567890';
var r = /(.+)(.+)(.+)/;
console.log(r.exec(s);
// ["1234567890", "12345678", "9", "0", index: 0, input: "1234567890", groups: undefined]

String 对象的正则方法

replace

语法如下:

str.replace(regexp|substr, newSubStr|function)

接受两个参数,第一个参数是要替换的文本,可以是正则或字符串,如果是字符串则作为检索的直接量文本,第二个是字符串或函数,如果是字符串,该字符串中可以内插一些特殊的变量名。如果是函数,该函数的返回值将替换掉第一个参数匹配到的结果。

var str = 'Twas the night before Xmas...';
var newstr = str.replace(/xmas/i, 'Christmas');
console.log(newstr);  // Twas the night before Christmas...

如果第二个参数是字符串,可以插入如下特殊变量名:

变量名 代表的值
? 插入一个 "$"。
$& 插入匹配的子串。
$` 插入当前匹配的子串左边的内容。
$' 插入当前匹配的子串右边的内容。
$n 假如第一个参数是 RegExp对象,并且 n 是个小于100的非负整数,那么插入第 n 个括号匹配的字符串。提示:索引是从1开始
// 交换字符串中的两个单词
var re = /(\w+)\s(\w+)/;
var str = "John Smith";
var newstr = str.replace(re, "$2, $1");
console.log(newstr); // Smith, John
var s = "2017-1-7";
var r = /(\d+)\-(\d+)\-(\d+)/;  // 分成三组
var a = s.replace(r,"$1年$2月$3日");
var b = s.replace(r,"$1/$2/$3");
console.log(a);  // 2017年1月7日
console.log(b);  // 2017/1/7

替换的时候,第二个参数可以是函数,函数的参数为匹配到的数组的散列值(相当于用exec方法匹配到的数组)

var s = "2017-1-7";
var r = /(\d+)\-(\d+)\-(\d+)/;  //分成三组
var a = s.replace(r, function (a,b,c,d) {
    return b + "年" + c + "月" + d + "日";
    //a是匹配到的结果
    //b是第一组匹配到的结果
    //c是第二组匹配到的结果
    //d是第三组匹配到的结果
});
console.log(a); //2017年1月7日

电话号码加密:

var list = [
    '12345678901',
    '12345678912',
    '12345678923',
    '12345678934',
    '12345678945',
    '12345678956'
];
var r = /(\d{4})(\d{4})(\d{3})/;
var newArr = list.map(function (v) {
   return  v.replace(r, function (a,b,c,d) {
        return b + "********".slice(0,c.length) + d;
    });
});
console.log(newArr);

match

参数为一个 正则表达式 对象,如果传入一个非正则表达式对象,则会隐式地使用 new RegExp(obj) 将其转换为一个 RegExp

  • 如果使用 g 标志,则将返回与完整正则表达式匹配的所有结果(Array),但不会返回捕获组,或者未匹配 null。
  • 如果未使用 g 标志,则仅返回第一个完整匹配及其相关的捕获组(Array)。 在这种情况下,返回的项目将具有如下所述的其他属性,或者未匹配 null。
    • groups: 一个捕获组数组 或 undefined(如果没有定义命名捕获组)。
    • index: 匹配的结果的开始位置
    • input: 搜索的字符串.

如果正则表达式不包含 g 标志,str.match() 将返回与 RegExp.exec(). 相同的结果,如下面例子所示。

var str = 'For more information, see Chapter 3.4.5.1';
var re = /see (chapter \d+(\.\d)*)/i;
var found = str.match(re);

console.log(found);

// logs [ 'see Chapter 3.4.5.1',
//        'Chapter 3.4.5.1',
//        '.1',
//        index: 22,
//        input: 'For more information, see Chapter 3.4.5.1' ]

// 'see Chapter 3.4.5.1' 是整个匹配。
// 'Chapter 3.4.5.1' 被'(chapter \d+(\.\d)*)'捕获。
// '.1' 是被'(\.\d)'捕获的最后一个值。
// 'index' 属性(22) 是整个匹配从零开始的索引。
// 'input' 属性是被解析的原始字符串。

如果 表达式中包含 g 标:

var str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
var regexp = /[A-E]/gi;
var matches_array = str.match(regexp);

console.log(matches_array);
// ['A', 'B', 'C', 'D', 'E', 'a', 'b', 'c', 'd', 'e']

split

语法为 str.split([separator[, limit]])

第一个参数可以为字符串或者正则表达式对象,如下所示:

var myString = "Hello 1 word. Sentence number 2.";
var split1 = myString.split(/\d/);
console.log(split1); // ["Hello ", " word. Sentence number ", "."]

// 如果包含括号,则其匹配结果将会包含在返回的数组中。
var split2 = myString.split(/(\d)/);
console.log(split2); // [ "Hello ", "1", " word. Sentence number ", "2", "." ]