正则表达式,本质上也是一门语言,而括号的存在使得正则表达式语言更为强大
对于括号 ( ) 的使用,其最原始的功能就是 分组 和 分支结构;而后在此基础上可以实现如:分组引用、反向引用
括号最原始的功能就是 分组 和 分支结构;强调的是括号内的表达式是一个整体,即提供子表达式
分组 和 分支结构
[1] 分组
分组通常由以下几种类型:
捕获型分组 (p)
非捕获型分组 (?:p )
正向前瞻型分组 (?=p) ,表示当前位置后面要有什么
反向前瞻型分组 (?!p) ,表示当前位置后面不能有什么
其中,正向前瞻型和反向前瞻型分组其实就是位置匹配,分别是正向先行断言和负向先行断言;
/// 分组 (捕获型分组)
regex = /(ab)+/g
string = 'ababa abbbb abababa'
console.log( string.match(regex) )
// => [ 'abab', 'ab', 'ababab' ]
string.match(regex)
console.log( RegExp.$1 )
// => ab
/// 非捕获型分组
/// 如果只想要括号最原始的功能,但不会引用它
regex = /(?:ab)+/g
string = 'ababa abbbb abababa'
console.log( string.match(regex) )
// => [ 'abab', 'ab', 'ababab' ]
string.match(regex)
console.log( RegExp.$1 )
// =>
[2] 分支结构
在多选分支结构中,括号提供了分支表达式的所有可能性;(表达式1|表达式2|表达式3)
/// 分支结构 (p1|p2)
regex = /^I LOVE (JINGJING|LIJING) FOREVER$/
console.log( regex.test('I LOVE LIJING FOREVER') )
console.log( regex.test('I LOVE JINGJING FOREVER') )
分组引用
其最重要的作用就是 数据提取 和 更强大的替换操作
/// 1. 提取数据
string = "2021-01-27"
// 不带分组
regex = /\d{4}-\d{2}-\d{2}/
console.log( string.match(regex) )
// => [ '2021-01-27', index: 0, input: '2021-01-27', groups: undefined ]
// 带分组
regex = /(\d{4})-(\d{2})-(\d{2})/
console.log( string.match(regex) )
// => [ '2021-01-27', '2021', '01', '27', index: 0, input: '2021-01-27', groups: undefined ]
/// 使用正则实例对象的 exec 方法
console.log( regex.exec(string) )
// => [ '2021-01-27', '2021', '01', '27', index: 0, input: '2021-01-27', groups: undefined ]
/// 使用 $1 - $9 提取对应的分组数据
// regex.test( string )
// regex.exec(string)
string.match(regex)
console.log( RegExp.$1 ) // => 2021
console.log( RegExp.$2 ) // => 01
console.log( RegExp.$3 ) // => 27
/// 2. 替换操作
/// 板栗儿:yyyy-mm-dd => mm/dd/yyyy
regex = /(\d{4})-(\d{2})-(\d{2})/
string = "2021-01-28"
let result = string.replace(regex, "$2/$3/$1")
console.log( result )
// => 01/28/2021
/// 等价于
result = string.replace( regex, function () {
return RegExp.$2 + "/" + RegExp.$3 + "/" + RegExp.$1
})
console.log( result )
// => 01/28/2021
/// 也等价于
result = string.replace( regex, (match, year, month, day) => {
console.log( match, year, month, day)
return `${month}/${day}/${year}`
})
console.log( result )
// 2021-01-28 2021 01 28
// 01/28/2021
反向引用
在正则本身里面引用分组,且只能引用之前出现的分组,即为反向引用;使用 \1, \2, \3 ... 进行分组编号的匹配,从而实现对应分组的引用;如果引用的分组不存在,则会直接匹配字符本身
/// 反向引用
/// 在正则本身里引用分组,但只能引用之前出现的分组,即反向引用
/// 静静仙女:龙哥哥,现在有个想法,想写一个正则支持匹配如下三种格式
/// 2021-01-27 2021/01/27 2021.01.27
/// 憨憨龙:这个简单,请看:
regex = /\d{4}(-|\/|\.)\d{2}(-|\/|\.)\d{2}/
console.log( regex.test('2021-01-27') ) // => true
console.log( regex.test('2021/01/27') ) // => true
console.log( regex.test('2021.01.27') ) // => true
console.log( regex.test('2021-01/27') ) // => true
/// 静静仙女:但是你这个同时也匹配了 2021-01/27 ,我想要保证分隔符前后一致
/// 憨憨龙:Nice! 这个也简单,使用反向引用 "\1" ( 其实就是匹配分组编号 \Group#1 ) 就可以解决,请看:
regex = /\d{4}(-|\/|\.)\d{2}\1\d{2}/
console.log( regex.test('2021-01-27') ) // => true
console.log( regex.test('2021/01/27') ) // => true
console.log( regex.test('2021.01.27') ) // => true
console.log( regex.test('2021-01/27') ) // => false
思维导图
案例分析
案例一:模拟字符串的 trim() 方法
/// 模拟实现字符串 trim 方法
/// trim() 方法是去掉字符串开头和结尾的空白字符
/// 方法一,直接匹配开头或结尾的空白字符 (效率高)
const trimFirst = str => {
return str.replace(/^\s+|\s+$/g, '')
}
console.log( trimFirst(" aijingjing ") )
// => aijingjing
/// 方法二,匹配整个字符串,用引用来提取相应的数据
const trimSecond = str => {
return str.replace(/^\s*(.*?)\s*$/g, '$1')
}
console.log( trimSecond(" aijingjing ") )
// => aijingjing
/// *? 惰性匹配,避免匹配到最后一个空格之前所有的空格,使得效率变得更低
案例二:将每个单词首字母转为大写
/// 将每个单词的首字母变成大写
const titleize = str => {
// return str.toLowerCase().replace(/(^|\s)\w/g, (char) => {
// return char.toUpperCase()
// })
return str.toLowerCase().replace(/(?:^|\s)\w/g, (char) => {
return char.toUpperCase()
})
}
console.log( titleize('i love jingjing') )
// => I Love Jingjing
/// 这里其实不需要使用非捕获匹配,只是为了演示非捕获括号的作用
案例三:字符串驼峰化
板栗儿:'-moz-transform ' => 'MozTransform'
/// 驼峰化
let camelize = str => {
return str.replace(/[-_\s]+(.)?/g, (match, char) => {
return char ? char.toUpperCase() : ''
})
}
console.log( camelize('-moz-transform ') )
// => MozTransform
/// 思考:如果我不想去掉 - 或者 _ 怎么办?
camelize = str => {
// 实现代码 ...
}
console.log( camelize('-moz-transform') )
// => -Moz-Transform
案例四:连接字符化或中划线化
/// 中划线化 或 连接字符化
const dasherize = str => {
return str.replace(/([A-Z])/g, '-$1').replace(/[-_\s]+/g, '-').toLowerCase()
}
console.log( dasherize('MozTransform') )
// => -moz-transform
案例五:XML 标签成对匹配
/// XML 标签成对匹配
/// 要求匹配:
/// <title>To JingJing</title>
/// <p>I LOVE YOU FOREVER!!!</p>
/// 不能匹配类似不成对的标签
/// <title>HAHAHAHAHA</p>
/// 分析:
/// 1, 匹配一个开标签,使用正则 <[^>]+>
/// 2, 撇配一个闭合标签,使用正则 <\/[^>]+>
/// 3, 成对匹配,可以使用反向引用
let regex = /<([^>]+)>[\d\D]*<\/\1>/
let string1 = "<title>To JingJing</title>"
let string2 = "<p>I LOVE YOU FOREVER!!!</p>"
let string3 = "<title>HAHAHAHAHA</p>"
console.log( regex.test(string1) ) // => true
console.log( regex.test(string2) ) // => true
console.log( regex.test(string3) ) // => false
/// HTML 转义 和 反转义
/// 将 HTML 特殊字符转换成对应的字符实体
const escapeHTML = str => {
let escapeChars = {
'<': 'lt',
'>': 'gt',
'"': 'quot',
'&': 'amp',
'\'': '#39'
}
// 使用正则构造函数生成正则,然后替换相应的数据
return str.replace( new RegExp(`[${Object.keys(escapeChars).join('')}]`, 'g'), (match) => {
return `&${escapeChars[match]};`
})
}
console.log( escapeHTML('<div>I LOVE MY LITTLE FAIRY</div>') )
// => <div>I LOVE MY LITTLE FAIRY</div>
/// 将字符实体转换为对应的 HTML 字符
const unescapeHTML = str => {
let htmlEntities = {
nbsp: ' ',
lt: '<',
gt: '>',
quot: '"',
amp: '&',
apos: '\''
}
return str.replace(/\&([^;]+);/g, (match, key) => {
console.log(match, key)
if (key in htmlEntities) {
return htmlEntities[key]
}
return match
})
}
console.log( unescapeHTML('<div>I LOVE MY LITTLE FAIRY</div>') )
// => <div>I LOVE MY LITTLE FAIRY</div>
源代码地址:regular-expression