乐趣区

javascript-正则表达式详细解读

正则表达式是用于匹配字符串中字符组合的模式。在 JavaScript 中,正则表达式也是对象。
这些模式被用于 RegExp 的 exec 和 test 方法, 以及 String 的 match、replace、search 和 split 方法。

正则表达式对象:

1、创建正则表达式的两种形式

1.1、使用字面量来创建构造函数

一个正则表达式字面量,其由包含在斜杠之间的模式组成 /pattern/flags
pattern: 正则表达式的文本。
flags: 正则表达式的标志
例如:全局查找含有 hello 字符的字符串,忽略字符的大小写

var regexp = /hello/gi
regexp.test("Hello world")        //true
regexp.test("Say hEllo")    //true

1.2、使用构造函数来创建正则表达式

使用构造函数提供正则表达式的运行时编译,如果你要动态生成一个正则表达式应该选用这种方式
new RegExp(pattern [, flags])
例如:

let regex = new RegExp("ab+c", "img");

构造函数可以接受正则表达式文本和字符串,作为第一个参数,正则表达式标志作为第二个参数

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

注意如果构造函数的第一个参数传入的是字符串,它须要经过以下几个步骤的处理:
1. 去掉正则表达式文本两头的 /
2. 正则表达式文本里面的所有 都要进行转义写成 \

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

当我们要做一个很复杂的查找时,可以像下面这样分解步骤

let str = "^&*()_ \nsdf7$df NN<NM<N 665 \n";
// 假如我要查找 str 里面【以字母开头】【中间可能含有多个数字】【以一个非单字字符 (特殊字符) 结尾】的字符串
// 首先是获取字母开头的正则:/^[a-zA-Z]+/
// 转化为字符串
let condition_1 = "^[a-zA-Z]+"

// 可能含有多个数字的正则:/[0-9]*/
// 转化为字符串
let condition_2 = "[0-9]*"
// 一个非单字字符(特殊字符):/\W/
// 转化为字符串
let condition_3 = "\\W"

// 最后按顺序拼接起来:
let regexp = new RegExp(`${condition_1}${condition_2}${condition_3}`, "img")
let result = str.match(regexp);
console.log(result)    //['SDF7123$']

2、正则表达式对象的 lastIndex 属性

lastIndex 是正则表达式实例的一个读 / 写整数属性,它指定从哪个索引开始下一个匹配。
The lastIndex is a read/write integer property of regular expression instances that specifies the index at which to start the next match.

var regex1 = new RegExp("ble", "g");
var str1 = 'table football, foosball';

var judage1 = regex1.test(str1);    
console.log(judage1);                //true
console.log(regex1.lastIndex);       //5 

var judage2 = regex1.test(str1);   
console.log(judage2);                //false
console.log(regex1.lastIndex);       //0 

这里我们可以看到同一个全局检索的正则表达式 (含有 g),执行多次后,检索的内容竟然是不同的。judage1 为 true,judage2 为 false。因为第一次检索后 lastIndex 会被改变,指向它匹配字符所在处的最后一个索引 +1。而之后再次调用正则检索,正则会从上一次 lastIndex 停止的地方开始检索。所以第一次匹配能够匹配到“ble”,而第二次从上一次“ble”停下的地方开始检索,后面没有“ble”字符,所以 judage2 = false。
⭐注意:
如果正则表达式设置了全局匹配标志 g,test() 和 exec()的执行会改变正则表达式 lastIndex 属性。连续的执行 test()方法,后续的执行将会从 lastIndex 处开始匹配字符串

var regex1 = new RegExp("ble", "g");
var str1 = 'table football, foosball';
console.log(regex1.test(str1))        //true
console.log(regex1.test(str1))        //false

因为多次调用设置了全局匹配标志 g 的 test(), 会导致 regex1 的 lastIndex 属性改变。所以千万不要在 for 循环里面调用含有全局匹配标志 g 的正则函数,那样匹配验证会出问题。

3、编写一个正则表达式的模式

3.1、使用简单的模式

let regexp = /abc/;        // 直接匹配在一个字符串中,是否含有字符 'abc'
regexp.test('123abc @#$hh')        //true

3.2、使用特殊字符

let regexp = /ab*c/                //* 是特殊字符,意思是前面一项出现了零个或者多个
regexp.test('123ac @#$hh')                //true
regexp.test('123abbbbbbbbbbc @#$hh')      //true  
regexp.test('123abdc @#$hh')              //false  

4、正则表达式有哪些特殊字符

4.1、简单的特殊字符

字符 含义
\ 是一个转义字符
1. 放在普通字符前面将后面的普通字符转义成特殊字符
/d/ 是匹配一个字符 d,而 /\d/ 就是匹配一个数字
2. 放在特殊字符前面,将后面特殊字符转化成普通字符
/A*/ 是匹配 0 个至多个 A,/A\*/ 就真的是匹配 ‘A*’ 字符
3. 使用 new RegExp(“pattern”) 的时候不要忘记将 \ 进行转义,因为 在字符串里面也是一个转义字符。
new RegExp(“\d”) 只匹配 d 字符,new  RegExp(“\\d”)  才会匹配数字
^ 匹配输入的开始。如果设置了多行标志 m,那么也匹配换行符后紧跟的位置。
例如,/^A/ 并不会匹配 “an A” 中的 ‘A’,但是会匹配 “An E” 中的 ‘A’。
/^w/m 并不会匹配 “Hello world” 中的 ‘w’, 也不会匹配 “Hellon world”(w 前面有空格),但是会匹配 “Hello \nworld”
$ 匹配输入的结束。如果设置了多行标志 m,那么也匹配换行符前的位置。
例如,/t$/ 并不会匹配 “eater” 中的 ‘t’,但是会匹配 “eat” 中的 ‘t’。
* 匹配前一个字符 0 次或多次。等价于 {0,}。
例如,/bo*/ 会匹配 “A ghost boooooed” 中的 ‘booooo’ 和 “A bird warbled” 中的 ‘b’,但是在 “A goat grunted” 中将不会匹配任何东西。
注意 /bo*/ 意思是以 b 字母开头,后面有 0 个或多个 o,而不是有 0 个或多个 bo
* 只针对离它最近的字符
/(bo)*/ 意思才是匹配 0 个或多个 ’bo’
. (小数点)匹配除换行符之外的任何单个字符。
例如,/.n/ 将会匹配 “nay, an apple is on the tree” 中的 ‘an’ 和 ‘on’,但是不会匹配 ‘nay’,因为 ‘.’ 会将每条字符串的首部视作有一个换行符
x|y 匹配‘x’或者‘y’。
例如,/green|red/ 匹配“green apple”中的‘green’和“red apple”中的‘red’
{n} n 是一个正整数,匹配了前面一个字符刚好发生了 n 次。
比如,/a{2}/ 不会匹配“candy”中的 ’a’, 但是会匹配“caandy”中所有的 a,以及“caaandy”中的前两个 ’a’。
{n,m} n 和 m 都是整数。匹配前面的字符至少 n 次,最多 m 次。如果 n 或者 m 的值是 0,这个值被忽略。
例如,/a{1, 3}/ 并不匹配“cndy”中的任意字符,匹配“candy”中的 a,匹配“caandy”中的前两个 a,也匹配“caaaaaaandy”中的前三个 a。注意,当匹配”caaaaaaandy“时,匹配的值是“aaa”,即使原始的字符串中有更多的 a。
注意:{n,m}之间没有空格
[xyz] 一个字符集合。匹配方括号中的任意字符,包括转义序列。你可以使用破折号(-)来指定一个字符范围。
对于点(.)和星号(*)这样的特殊符号,如果不进行转义,在一个字符集中没有特殊的意义。
/[a\d]/g 只会匹配“add 20190726”中的‘a’和‘d’,并不会匹配数字
如果进行了转义
/[a\\d]/g 就会匹配“add 20190726”中的‘a’和所有数字
[^xyz] 一个反向字符集。也就是说,它匹配任何没有包含在方括号中的字符。你可以使用破折号(-)来指定一个字符范围。
使用方法同 [xyz], 只是 [^xyz] 是不包含
例如,[^abc] 和 [^a-c] 是一样的。他们匹配 ”brisket” 中的‘r’,也匹配“chop”中的‘h’。
\d 匹配一个数字。
等价于 [0-9]。
例如,/\d/ 或者 /[0-9]/ 匹配 ”B2 is the suite number.” 中的 ’2’。
\D 匹配一个非数字字符。
等价于 [^0-9]。
例如,/\D/ 或者 /[^0-9]/ 匹配 ”B2 is the suite number.” 中的 ’B’。
\w 匹配一个单字字符(字母、数字或者下划线)。
等价于 [A-Za-z0-9_]。
例如, /\w/ 匹配 “apple,” 中的 ‘a’,”$5.28,” 中的 ‘5’ 和 “3D.” 中的 ‘3’。
\W 匹配一个非单字字符。
等价于 [^A-Za-z0-9_]。
例如, /\W/ 或者 /[^A-Za-z0-9_]/ 匹配 “50%.” 中的 ‘%’

4.2、高级的特殊字符

更新中

正则表达式对象常用的 3 种方法:

1、RegExp.prototype.toString()

RegExp 对象并没有继承 Object.prototype.toString(),而是覆盖了 Object 对象的 toString() 方法,对于 RegExp 对象,toString 方法返回一个该正则表达式的字符串形式。

myExp = new RegExp("a+b+c");
console.log(myExp.toString());       // 显示 "/a+b+c/"

foo = new RegExp("bar", "g");
console.log(foo.toString());         // 显示 "/bar/g"

2、RegExp.prototype.test()

test() 方法执行一个检索,用来查看正则表达式与指定的字符串是否匹配。返回 true 或 false。
即一个字符串是否包含满足这个正则表达式的子字符串

let _regexp = new RegExp("Wo" , "i");
let str = 'hello world!';
let result = _regexp.test(str);
console.log(result);    //true

3、RegExp.prototype.exec()

exec() 方法在一个指定字符串中执行一个搜索匹配。返回一个结果数组或 null。
如果匹配成功,exec() 方法返回一个数组,并更新正则表达式对象的属性。返回的数组将完全匹配成功的文本作为第一项,将正则括号里匹配成功的作为数组填充到后面。

var params = "user"
var regexp = new RegExp("[?#&]" + params + "=([^&#]*)", "i")
var str = "www.qq.com?user=Tom&psw=123456";
var res = regexp.exec(str);
console.log(res)    // ["?user=Tom", "Tom"]

可以看到返回数组的第一个元素就是整个的匹配模式所匹配到的字符串,而第二个匹配到的字符恰好是参数值。这是 exec 匹配返回的规则:第一个元素为整个的匹配字符串,从第二个参数开始返回模式中每一个 () 自身正则所匹配的字符串。这里面 ([^]*) 返回的就是不以 & 或 #开头的字符串,即等号后面对应的参数 Tom。

然后我们给 params 再加一个括号, 便把 params(匹配到的)也输出来了。

var params = "user"
var regexp = new RegExp("[?#&]" + "(" +params+ ")" + "=([^&#]*)", "i")
var str = "www.qq.com?user=Tom&psw=123456";
var res = regexp.exec(str);
console.log(res)    // ["?user=Tom", "user", "Tom"]

String 对象使用正则的几种方法

1、match 方法

match 方法检索返回一个字符串匹配正则表达式的的结果。
当你不使用 g 标志,即不进行全局检索时,仅返回第一个完整匹配及其相关的捕获组(Array)。

let str = "……ssdf #$%^& ab Abbbbb ABC153**";
let regexp = new RegExp("ab", "i");
let result = str.match(regexp)
console.log(result)

输出:[
  'ab',         // 第一个完整的匹配项       
  index: 13,    // 匹配结果的开始位置
  input: '……ssdf #$%^& ab Abbbbb ABC153**',        // 搜索的字符串
  groups: undefined        // 一个捕获组数组 或 undefined(如果没有定义命名捕获组)]

如果 match 没有匹配到任何项,则会直接返回 null

let regexp2 = new RegExp('xxx', 'i');
let result2 = str.match(regexp2)
//null

⭐注意 match 还有两种特殊的使用情况:
1、match 如果传入一个非正则表达式对象,则会隐式地使用 new RegExp(obj) 将其转换为一个 RegExp

let result3 = str.match("ab")    //str.match("ab") = str.match(new RegExp("ab"));

输出还是和之前一样:[
  'ab',
  index: 13,
  input: '……ssdf #$%^& ab Abbbbb ABC153**',
  groups: undefined
]

2、如果你没有给出任何参数并直接使用 match() 方法,你将会得到一 个包含空字符串的 Array:[“”],而不是 null。

let result 4 = str.match()

输出:[
  '',
  index: 0,
  input: '……ssdf #$%^& ab Abbbbb ABC153**',
  groups: undefined
]

2、replace 方法

通过正则查找到匹配项,然后替换这些字符,注意 replace 方法只会返回替换后的结果,不会改变原字符串。
使用示例:
1、直接替换字符串默认只会替换第一次匹配的项。

let str = "……ssdf #$%^& ab Abbbbb ABC153**";
let result = str.replace("s", 77)
console.log(result)        //……77sdf #$%^& ab Abbbbb ABC153**
默认只会替换第一个 s 

2、正则加 g 标志,全局替换所有匹配的字符

let str = "……ssdf #$%^& ab Abbbbb ABC153**";
let reg = /ab/gi
let result = str.replace(reg, "T")
console.log(result)        //……ssdf #$%^& T Tbbbb TC153**
所有的 ab 都替换成了 T

3、替换字符串支持插入下面的特殊变量名

① $& 插入匹配的子串

let str = "刘一、陈二、张三、李四、王五、赵六、孙七、周八、吴九、郑十"
let reg = new RegExp("[张赵孙][三七八]", "gi")
let result = str.replace(reg, "新名字")
console.log(result)
// 刘一、陈二、新名字、李四、王五、赵六、新名字、周八、吴九、郑十

let result2 = str.replace(reg, "新名字 $&");
console.log(result2)
// 刘一、陈二、新名字张三、李四、王五、赵六、新名字孙七、周八、吴九、郑十

② $` 插入当前匹配的子串左边的内容。

let result3 = str.replace(reg, "新名字 $`");
console.log(result3)
// 刘一、陈二、新名字刘一、陈二、、李四、王五、赵六、新名字刘一、陈二、张三、李四、王五、赵六、、周八、吴九、郑十

③ $’ 插入当前匹配子串右边的内容

④ $n 插入第 n 个括号匹配的字符串
⭐注意:
new RexExp() 第一个参数需要是一个含有正则的文本
n 是个小于 100 的非负整数
索引是从 1 开始(即插入第一个括号里面匹配的内容为 $1)
例子:

var re = /(\w+)\s(\w+)/;
var str = "John Smith";
var newstr = str.replace(re, "$2, $1");
console.log(newstr);    // Smith, John
// 分析第一个括号里面匹配到了 John 即 $1 是 John,第二个括号里面匹配到了 Smith,即 $2 为 Smith

3、search 方法

如果匹配成功,则 search() 返回正则表达式在字符串中首次匹配项的索引; 否则,返回 -1。

let str = "Hello AbC aBC ABC abc"
let result = str.search(/abc/gi);
console.log(result)    //6
// 注意即使你用了全局查找标志 g,search 也只会返回首次匹配的索引,索引走 0 开始

4、Split 方法

// split 方法默认搜素全局,所以不管你的正则是否使用了 g 标志,结果都一样
let str = "Hello AbC aBC ABC abc"
let result = str.split(/[a]/);
let result2 = str.split(/[a]/g);
console.log(result)        //['Hello AbC', 'BC ABC', 'bc']
console.log(result2)       //['Hello AbC', 'BC ABC', 'bc']

//split 可以限制分割几块字符串,当返回的分割字块到达限制大小时分割停止
let result3 = str.split(/[a]/g, 2);
console.log(result3)        //['Hello AbC', 'BC ABC']

// 当分割的限制大于返回数组的长度时
let result3 = str.split(/[a]/g, 3000);
console.log(result3)        //['Hello AbC', 'BC ABC', 'bc']

参考资料
MDN web docs 正则表达式

退出移动版