学会正则表达式

一、RegExp 对象

JavaScript 通过内置对象 RegExp 支持正则表达式
有两种方法实例化 RegExp 对象:

1. 字面量

定义变量,把正则表达式的文本写在两个斜线之间
例如:var reg = /\bis\b/

2. 构造函数

var reg = new RegExp('\\bis\\b', 'g')(在 js 中,反斜线 \ 本身是特殊字符,使用的话,要进行转义)

3. 修饰符

  • g:global全文搜索。不添加的话,之搜索到第一个匹配停止
  • i:ignore casehu忽略大小写,默认大小写敏感
  • m:multiple lines多行搜索
var reg = /\bis\b/
'He is a boy, This is dog, where is she'.replace(reg, 'IS')
"He IS a boy, This is dog, where is she?" // 只匹配了第一个
var reg = /\bis\b/g // 加上 g 修饰符
'He is a boy, This is dog, where is she'.replace(reg, 'IS')
"He IS a boy, This IS dog, where IS she" // 全文匹配
var reg = /\bis\b/g
'He is a boy. Is she?'.replace(reg, 'Q')
"He Q a boy. Is she?" // 默认对 大小写敏感
var reg = /\bis\b/gi
'He is a boy. Is she?'.replace(reg, 'Q')
"He Q a boy. Q she?" // 忽略大小写

二、字面量字符和元字符

1. 字面量字符(原义文本字符)

在正则表达式中,某个字符只表示它字面的含义

/love/.test('I love you') // true

2. 元字符

在正则表达式中有特殊含义的特殊字符
比如:. * + ? $ ^ | \ - () [] {}

image.png

3. 字符类

  • 一般情况下,正则表达式一个字符对应字符串的一个字符
  • 表达式 ab\t 就是一对一

匹配某类字符:
问题:如果我们希望匹配的是符合一系列特征的某类字符,而不是某个,该怎么做呢?

  • 我们可以使用云字符 [] 来构建一个简单的类
  • 所谓的类是指符合某些特征的对象,一个泛指,而不是特指某个字符
  • 表达式 [abc] 把字符 abc 归为一类,表达式可以匹配这类的字符
  • 这一类字符出现任意一个都可以匹配
'a1b2c3d4'.replace(/[abc]/g, 'X')
"X1X2X3d4"

反向字符类:

  • 使用元字符 ^ 创建 反向类/负向类
  • 反向类的意思是不属于某类的内容
  • 表达式 [^abc] 是指不是 abc 的内容
'a1b2c3d4'.replace(/[^abc]/g, 'X')
"aXbXcXXX" // 把 abc 以外的内容都替换

4. 范围类

  • 我们可以使用 [a-z] 表示从 a 到 z 的任意小写字母
  • 这是个闭区间,包括 a 和 z 本身
'a1b2c3d4'.replace(/[a-z]/g, 'Q')
"Q1Q2Q3Q4"
  • [] 组成的类内部是可以连写的 [a-zA-Z]
'a1b2c3d4ANSMDE'.replace(/[a-zA-Z]/g, 'Q')
"Q1Q2Q3Q4QQQQQQ"

如果我们相匹配范围类中间的横线 - ,该怎么做呢?

'2019-08-07'.replace(/[0-9]/g, 'b')
"bbbb-bb-bb"
'2019-08-07'.replace(/[0-9-]/g, 'b')
"bbbbbbbbbb"
'2019-08-07'.replace(/[-]/g, 'b')
"2019b08b07"

5. 预定义类及边界

  • 预定义类

预定义类

比如:匹配一个 ab+数字+任意字符 的字符串
ab[0-9][^/n/r] === ab\d.

  • 边界

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


边界匹配字符
'This is a dog'.replace(/is/g, 0)
"Th0 0 a dog"
'This is a dog'.replace(/\bis\b/g, 0)
"This 0 a dog"
'This is a dog'.replace(/\Bis\b/g, 0)
"Th0 is a dog"
'@123@abc@'.replace(/@./g, 'Q')
"Q23Qbc@"
'@123@abc@'.replace(/^@./g, 'Q')
"Q23@abc@"
'@123@abc@'.replace(/.@/g, 'Q')
"@12QabQ"
'@123@abc@'.replace(/.@$/g, 'Q')
"@123@abQ"

6. 量词

我们希望匹配一个连续出现多次的字符串


量词

7. js 正则贪婪模式和非贪婪模式

贪婪模式:当进行多次重复类匹配时,js 正则默认尽可能多的匹配,直到下一个字符不满足匹配规则为止
非贪婪模式:让正则表达式尽可能少的匹配,也就是说一旦匹配成功就不再继续尝试
比如:

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

8. 分组

问题: 匹配字符串 Byron 连续出现三次的场景
我们可能这样写:Byron{3},但并没有出现理想的效果

image.png

所以,

  • 我们可以使用 () 达到分组的功能,使量词作用于分组

可以这样写:(Byron){3}

'a1b2c3d4'.replace(/[a-z]\d{3}/g, 'X')
"a1b2c3d4"
'a1b2c3d4'.replace(/([a-z]\d){3}/g, 'X')
"Xd4"
  • 或:使用 | 可以达到 或 的效果
    image.png
'ByronCasper'.replace(/Byron|Casper/g, 'X')
"XX"
'ByronsperByrCasper'.replace(/Byr(on|Ca)sper/g, 'X')
"XX"
  • 反向引用:
    使用 $1, $2, $3... 可对分组进行捕获
    比如:把2019-08-07 变成 08/07/2019
    image.png
'2019-08-07'.replace(/\d{4}-\d{2}-\d{2}/g, 'X')
"X"
'2019-08-07'.replace(/(\d{4})-(\d{2})-(\d{2})/g, '$2/$3/$1')
"08/07/2019"
  • 忽略分组:
    不希望捕获某些分组,只需要在分组内加上 ?: 就可以啦
    (?:Byron).(ok)
    image.png

9. 前瞻

之前我们的正则只是规则匹配,比如我们找“张三”,直接匹配出“张三”就行了
现在我们不仅要找出“张三”,还要看“张三”的父亲是不是“张二”(断言)

  • 正则表达式从头部向尾部开始解析,文本尾部方向,成为 “前”
  • 前瞻就是正则表达式匹配到规则的时候,向前检查是否符合断言,后顾/后瞻与之方向相反
  • JavaScript不支持后顾
  • 符合和不符合特定断言成为 肯定/正向 匹配和 否定/负向 匹配
image.png

image.png
'a2*3'.replace(/\w\d/g, 'X')
"X*3"
'a2*3'.replace(/\w(?=\d)/g, 'X')
"X2*3"
'a2*34v8'.replace(/\w(?=\d)/g, 'X')
"X2*X4X8"
'a2*34vv'.replace(/\w(?=\d)/g, 'X')
"X2*X4vv"
'a2*34v8'.replace(/\w(?!\d)/g, 'X')
"aX*3XvX"

三、正则实例对象的属性

  • global:是否全文搜索。默认 false
  • ignore case:是否忽略大小写。默认 false (不忽略)
  • multiline:多行搜索,默认 false
  • lastIndex:是当前表达式匹配内容的最后一个字符的下一个位置
  • source:正则表达式的文本字符串

以上属性除了 lastIndex 都是只读的,lastIndex 可写,可手动设置 indexIndex 的值,就会从指定位置开始匹配

四、正则实例对象的方法

1. RegExp.prototype.test()

返回一个布尔值,表示当前模式是否能够匹配参数字符串

注意:

  • 如果正则表达式没有 g 修饰符,每次匹配从参数字符串的第 0 个位置开始
  • 如果正则表达式带有 g 修饰符,每次匹配都从上一次结束的位置向后匹配(lastIndex),直到最后匹配不成功,继续从头开始匹配
  • lastIndex 属性只对同一个正则表达式有效
  • 使用 test 方法时一般不需要加 g 标志
var reg = /\w/
var reg1 = /\w/g
reg.test('a')
true
reg.test('ab')
true
reg1.test('ab')
true
reg1.lastIndex
1
reg1.test('ab')
true
reg1.lastIndex
2
reg1.test('ab')
false
reg1.lastIndex
0
reg1.test('ab')
true
// 每次调用方法,就新创建了一个正则,lastIndex 在非全局下不生效,所以 lastIndex 始终是 0
/\w/g.test('ab')
true
/\w/g.test('ab')
true
/\w/g.test('ab')
true
/\w/g.lastIndex
0

2. RegExp.prototype.exec()

用来返回匹配结果。如果发现匹配,就返回一个数组,成员是匹配成功的子字符串;如果匹配不成功,就返回 null
exec方法的返回数组还包含以下两个属性:

  • index:整个模式匹配成功的开始位置(从 0 开始计数)
  • input:整个原字符串

如果正则表达式包含“圆括号”(即含有“组匹配”),则返回的数组会包括多个成员

  • 第一个成员是整个匹配成功的结果
  • 第二个成员对应第一个括号匹配成功的结果
  • 第三个成员对应第二个括号匹配成功的结果
  • ......
var reg1 = /\d(\w)(\w)\d/
var reg2 = /\d(\w)(\w)\d/g
var ts = '$1az2bb3cy4dd5ee'
var ret = reg1.exec(ts)
console.log(reg1.lastIndex + '\t' + ret.index + '\t' + ret.toString())
"0  1   1az2,a,z"
console.log(reg1.lastIndex + '\t' + ret.index + '\t' + ret.toString())
"0  1   1az2,a,z"
while(ret = reg2.exec(ts)){
    console.log(reg2.lastIndex + '\t' + ret.index + '\t' + ret.toString())
}
"5  1   1az2,a,z"
"11 7   3cy4,c,y"

五、字符串的实例方法

1. String.prototype.search()

  • 返回第一个满足条件的匹配结果在整个字符串中的 index
  • 如果没有任何匹配,则返回 -1
  • search() 方法不执行全局匹配,它将忽略标志 g,并且总是从字符串的开始进行检索
'X1Y2'.search(1)
1
'X1Y2'.search('1')
1
'X1Y2'.search(/X/)
0
'X1Y2'.search(/10/)
-1

问题:为什么 search() 方法的第一个参数是数字也行呢?
因为这些字符串方法都与正则有关,当第一个参数不是正则的时候,这个方法会尝试把第一个参数转为正则

2. String.prototype.match()

  • 对字符串进行正则匹配,返回匹配结果
  • 匹配成功,返回一个数组
  • 匹配失败,返回 null

非全局匹配:

  • 如果 regexp 没有标志 g,那么 match() 方法只能在字符串中执行一次匹配
  • 如果没有找到任何匹配的文本,返回 null
  • 否则它将返回一个数组,其中存放了与它找到的匹配文本有关的信息
  • 返回的数组的第一个元素存放的是匹配文本,而其余的元素存放的是与正则表达式的子表达式匹配(组匹配)的文本
  • 除了常规的数组元素外,返回的数组还含有两个对象属性:
    • index:整个模式匹配成功的开始位置(从 0 开始计数)
    • input:整个原字符串
var reg1 = /\d(\w)(\w)\d/
var reg2 = /\d(\w)(\w)\d/g
var ts = '$1az2bb3cy4dd5ee'
var ret = ts.match(reg1)
console.log(ret)
(3) ["1az2", "a", "z", index: 1, input: "$1az2bb3cy4dd5ee", groups: undefined]
console.log(ret.index + '\t' + reg1.lastIndex)
"1  0"

全局匹配:

  • 如果 regexp 具有标志 g,那么 match() 方法将执行全局检索,找到字符串中的所有匹配子字符串,然后一次性返回所有匹配结果
    • 没有找到任何匹配的字串,返回 null
    • 如果找到了一个或多个匹配子串,则返回一个数组
  • 数组元素中存放的是字符串中的所有的匹配子串,没有了分组的信息,而且也没有 index 属性和 input 属性,
var reg1 = /\d(\w)(\w)\d/
var reg2 = /\d(\w)(\w)\d/g
var ts = '$1az2bb3cy4dd5ee'
var ret = ts.match(reg2)
console.log(ret)
(2) ["1az2", "3cy4"] // 没有了组匹配
console.log(ret.index + '\t' + reg1.lastIndex)
"undefined  0"

3. String.prototype.split()

  • 我们常使用 split 方法把字符串分割为字符数组
'a,b,c,d'.split(',') // ["a", "b", "c", "d"]
  • 在一些复杂的分割情况下,我们可以使用正则进行分割
'a1b2c3d'.split(/\d/) // ["a", "b", "c", "d"]

4. String.prototype.replace()

三种形式:

  • String.prototype.replace(str, replaceStr)
  • String.prototype.replace(reg, replaceStr)
  • String.prototype.replace(reg, function)
'a1a2a3'.replace('a', 'b')
"b1a2a3"
'a1a2a3'.replace(/a/, 'b')
"b1a2a3" // 不加 `g` 标识符,只替换第一个匹配成功的值
'a1a2a3'.replace(/a/g, 'b')
"b1b2b3"

去除字符串首尾空格

' Hello World '.replace(/^\s+|\s+$/g, '')
"Hello World"

replace() 第二个参数可以使用美元符号 $,用来指代所替换的内容

  • $&:匹配的子字符串
  • $`:匹配结果前面的文本
  • $':匹配结果后面的文本
  • $n:匹配成功的第 n 组内容
  • $$:指代美元符号 $
'Hello World'.replace(/(\w+)\s(\w+)/g, '$2 $1')
"World Hello"
'abc'.replace('b', '[$`-$&-$\']')
"a[a-b-c]c"

replace() 第二个参数可以是一个函数,函数返回值替换匹配的值
function 参数的含义:
function 会在每次匹配替换的时候调用(callback),有四个参数:

  • 第一个参数:匹配字符串
  • 第二个参数:正则表达式分组内容,没有分组就没有这个参数(可以没有,也可以有多个)
  • 第三个参数:匹配项在字符串中的 index
  • 第四个参数:原字符串
'a1b2c3d4e5'.replace(/\d/g, function(match, index, origin){
    console.log(index)
    return parseInt(match) + 1
})
1
3
5
7
9
"a2b3c4d5e6"

'a1b2c3d4e5'.replace(/(\d)(\w)(\d)/g, function(match, group1, group2, group3, index, origin){
    console.log(match)
    return group1 + group3
})
1b2
3d4
"a12c34e5"

你可能感兴趣的:(学会正则表达式)