作者:yrainly
公众号:前端开心果
正则表达式进阶内容
先补充下之前介绍过的replace()
方法的使用。
replace()方法详解
replace()
方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。
replace
方法有三种形态:
String.prototype.replace(str, replaceStr)
String.prototype.replace(reg, replaceStr)
String.prototype.replace(reg, function)
使用示例1:
const str = 'Hello Kaixinguo'
// 正则表达式
str.replace(/ello/, 'ey') // hey Kaixinguo
str.replace(/elll/, 'ey') // Hello Kaixinguo
// 字符串:字符串参数会转换为正则表达式
str.replace('ello', 'ey') // hey Kaixinguo
str.replace('elll', 'ey') // Hello Kaixinguo
str.replace(/o/g, function (...args) {
console.log(args)
})
// ['o', 4, 'Hello Kaixinguo']
// ['o', 14, 'Hello Kaixinguo']
replace
接受函数参数时,有四个参数:
- 匹配字符串
- 正则表达式分组内容,没有分组则没有该参数
- 匹配项在字符串中的
index
- 原字符串
上面的使用示例1,replace
接收函数参数时没有分组,所以打印出来的只有3个参数。
下面示例演示正则表达式使用了分组的情况。
使用示例2:
const str = 'Hello Kaixinguo,'
str.replace(/(\w{2})/g, function (...args) {
console.log(args)
})
// 有几个分组就有几个group
/**
* [match, group1, index, origin]
* ['He', 'He', 0, 'Hello Kaixinguo,']
['ll', 'll', 2, 'Hello Kaixinguo,']
['Ka', 'Ka', 6, 'Hello Kaixinguo,']
['ix', 'ix', 8, 'Hello Kaixinguo,']
['gu', 'gu', 12, 'Hello Kaixinguo,']
* **/
后行断言
支持先行断言(lookahead
),先行否定断言(negative lookahead
),后行断言(lookbehind
)和后行否定断言(negative lookbehind
)。ES2018 引入后行断言。
表达式 | 描述 |
---|---|
?=n | 匹配任何其后紧接指定字符串 n 的字符串 |
?!n | 匹配任何其后没有紧接指定字符串 n 的字符串 |
先行断言
x
只有在 y
前面才匹配,必须写成/x(?=y)/
。比如,只匹配百分号之前的数字,要写成/\d+(?=%)/
。
先行否定断言
x
只有不在 y
前面才匹配,必须写成/x(?!y)/
。比如,只匹配不在百分号之前的数字,要写成/\d+(?!%)/
。
使用示例:
// 先行断言
let behead = /\d+(?=%)/.exec('关注我的人中奖率甚至都达到了100%')
console.log(behead)
// ['100', index: 14, input: '关注我的人中奖率甚至都达到了100%', groups: undefined]
// 先行否定断言
let notBehead = /\d+(?!%)/.exec('有14个人关注了我,他们的中奖率能到100%')
console.log(notBehead)
// ['14', index: 1, input: '有14个人关注了我,他们的中奖率能到100%', groups: undefined]
上面两个字符串,如果互换正则表达式,就不会得到相同的结果。另外,还可以看到,“先行断言”括号之中的部分((?=%)
),是不计入返回结果的。
后行断言
正好与“先行断言”相反,x
只有在 y
后面才匹配,必须写成 /(?<=y)x/
。比如,只匹配美元符号后面的数字,要写成/(?<=$)\d+/
。
后行否定断言
则与“先行否定断言”相反,x
只有不在y
后面才匹配,必须写成 /(?<!y)x/
。比如,只匹配不在美元符号后面的数字,要写成 /(?<!$)\d+/
。
使用示例:
// 后行断言
let lookbehead = /(?<=$)\d+/.exec('关注我的人都会中$100万')
console.log(lookbehead)
// ['100', index: 9, input: '关注我的人都会中$100万', groups: undefined]
// 后行否定断言
let negativeLookbehead = /(?!=$)\d+/.exec('有14个人关注了我,他们都中了$100万')
console.log(negativeLookbehead)
// ['14', index: 1, input: '有14个人关注了我,他们都中了$100万', groups: undefined]
具名组匹配
正则表达式使用圆括号进行组匹配
const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/
上面代码中,正则表达式里面有三组圆括号。使用 exec
方法,就可以将这三组匹配结果提取出来。
const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/
const matchObj = RE_DATE.exec('1997-11-26')
console.log(matchObj)
// [ '1997-11-26', '1997', '11', '26', index: 0, input: '1997-11-26', groups: undefined ]
const year = matchObj[1] // 1997
const month = matchObj[2] // 11
const day = matchObj[3] // 26
组匹配的一个问题是,每一组的匹配含义不容易看出来,而且只能用数字序号(比如 matchObj[1]
)引用,要是组的顺序变了,引用的时候就必须修改序号。
ES2018 引入了具名组匹配,允许为每一个组匹配指定一个名字,既便于阅读代码,又便于引用。
const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
const matchObj = RE_DATE.exec('1997-11-26')
console.log(matchObj)
// [ '1997-11-26', '1997', '11', '26', index: 0, input: '1997-11-26', groups: [Object: null prototype] { year: '1997', month: '11', day: '26' } ]
const year = matchObj.groups.year // 1997
const month = matchObj.groups.month // 11
const day = matchObj.groups.day // 26
上面代码中,“具名组匹配”在圆括号内部,模式的头部添加“问号+尖括号+组名”(?<year>
),然后就可以在 exec
方法返回结果的 groups
属性上引用该组名。同时,数字序号 (matchObj[1]
) 依然有效。
具名组匹配等于为每一组匹配加上了ID,便于描述匹配的目的。如果组的顺序变了,也不用改变匹配后的处理代码。
如果具名组没有匹配,那么对应的 groups
对象属性会是 undefined
。
const reg = /^(?<as>a+)?$/
const matchObj = reg.exec('')
console.log(matchObj)
console.log(matchObj.groups.as) // undefined
console.log('as' in matchObj.groups) // true
/**
* [
'',
undefined,
index: 0,
input: '',
groups: [Object: null prototype] { as: undefined }
]
*/
上面代码中,具名组 as
没有找到匹配,那么 matchObj.groups.as
属性值就是 undefined
,并且 as
这个键名在 groups
是始终存在的。
解构赋值和替换
有了具名组匹配以后,可以使用解构赋值直接从匹配结果上为变量赋值。
let { groups: { one, two } } = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar')
console.log(one, two) // foo bar
字符串替换时,使用 $<组名>
引用具名组。
let reg = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u
console.log('2006-06-07'.replace(reg, '$<day>/$<month>/$<year>')) // 07/06/2006
上面代码中,replace
方法的第二个参数是一个字符串,而不是正则表达式。
replace
方法的第二个参数也可以是函数,该函数的参数序列如下。
let reg = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u
let str = '2015-01-02'.replace(reg, (
matched, // 整个匹配结果 2015-01-02
capture1, // 第一个组匹配 2015
capture2, // 第二个组匹配 01
capture3, // 第三个组匹配 02
position, // 匹配开始的位置 0
S, // 原字符串 2015-01-02
groups // 具名组构成的一个对象 {year, month, day}
) => {
let { day, month, year } = groups
return `${day}/${month}/${year}`
})
console.log(str) // 02/01/2015
具名组匹配在原来的基础上,新增了最后一个函数参数:具名组构成的一个对象。函数内部可以直接对这个对象进行解构赋值。
let reg = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u
let str1 = '2015-01-02'.replace(reg, (...args) => {
console.log(args)
let { day, month, year } = args[6]
return `${day}/${month}/${year}`
})
console.log(str1)
/**
* [
'2015-01-02',
'2015',
'01',
'02',
0,
'2015-01-02',
[Object: null prototype] { year: '2015', month: '01', day: '02' }
]
*/
引用
如果要在正则表达式内部引用某个“具名组匹配”,可以使用 \k<组名>
的写法。
const reg = /^(?<word>[a-z]+)!\k<word>$/
reg.test('abc!abc') // true
reg.test('abc!ab') // false
数字引用(\1
)依然有效。
const reg = /^(?<word>[a-z]+)!\1$/
reg.test('abc!abc') // true
reg.test('abc!ab') // false
这两种引用方法还可以同时使用。
const reg = /^(?<word>[a-z]+)!\k<word>!\1$/
reg.test('abc!abc!abc') // true
reg.test('abc!ab!ab') // false
最后,分享几个可以在线测试正则表达式的地址:
结语
❤️ 🧡 💛大家喜欢我写的文章的话,欢迎大家点点关注、点赞、收藏和转载!!
欢迎关注公众号前端开心果 🔥我会持续更新前端相关的内容文章哦。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。