一、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. 元字符
在正则表达式中有特殊含义的特殊字符
比如:. * + ? $ ^ | \ - () [] {}
3. 字符类
- 一般情况下,正则表达式一个字符对应字符串的一个字符
- 表达式
ab\t
就是一对一
匹配某类字符:
问题:如果我们希望匹配的是符合一系列特征的某类字符,而不是某个,该怎么做呢?
- 我们可以使用云字符
[]
来构建一个简单的类 - 所谓的类是指符合某些特征的对象,一个泛指,而不是特指某个字符
- 表达式
[abc]
把字符a
或b
或c
归为一类,表达式可以匹配这类的字符 - 这一类字符出现任意一个都可以匹配
'a1b2c3d4'.replace(/[abc]/g, 'X')
"X1X2X3d4"
反向字符类:
- 使用元字符
^
创建 反向类/负向类 - 反向类的意思是不属于某类的内容
- 表达式
[^abc]
是指不是a
或b
或c
的内容
'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}
,但并没有出现理想的效果
所以,
- 我们可以使用
()
达到分组的功能,使量词作用于分组
可以这样写:(Byron){3}
'a1b2c3d4'.replace(/[a-z]\d{3}/g, 'X')
"a1b2c3d4"
'a1b2c3d4'.replace(/([a-z]\d){3}/g, 'X')
"Xd4"
- 或:使用
|
可以达到 或 的效果
'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
'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)
9. 前瞻
之前我们的正则只是规则匹配,比如我们找“张三”,直接匹配出“张三”就行了
现在我们不仅要找出“张三”,还要看“张三”的父亲是不是“张二”(断言)
- 正则表达式从头部向尾部开始解析,文本尾部方向,成为 “前”
-
前瞻
就是正则表达式匹配到规则的时候,向前检查是否符合断言,后顾/后瞻与之方向相反 - JavaScript不支持后顾
- 符合和不符合特定断言成为
肯定/正向
匹配和否定/负向
匹配
'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"