正则元字符详解, 正则匹配, 正则捕获,常见正则表达式

文章目录

    • 正则表达式
    • 1.0 编写正则表达式
      • 1.1 正则表达式由两部分组成
        • 1.1.1 元字符
        • 1.1.2 修饰符
    • 2.0 正则表达式元字符详解
      • 2.1 ^和$ 元字符详解
      • 2.2 `\` 转义字符详解
      • 2.3 `x|y` 理解
      • 2.3 `[]`
    • 3.0 常用正则表达式练习
      • 3.1 验证是否为有效数字
      • 3.2 验证密码
      • 3.3 验证真实中文姓名的
      • 3.4 验证邮箱的正则表达式
      • 3.5 身份证号码正则
    • 4.0 两种创建正则表达式的区别.
    • 5.0 正则表达式的捕获
      • 5.1 怎么实现正则捕获
      • 5.2 捕获所有匹配结果
    • 6.0 正则表达式的分组捕获和分组引用
    • 7.0 正则表达式捕获的贪婪性
      • 7.1 正则中问号 ? 的五大作用
    • 8.0 正则表达式捕获的其他办法.
      • 8.1 test捕获
      • 8.2 replace 捕获

正则表达式

正则表达式(regular expression): 有一个 RegExp类

正则表达式是用来处理字符串的规则.

  • 只能处理字符串
  • 它是一个规则:
    • 可以验证字符串是否符合某个规则(test).
    • 也可以吧字符串当中符合规则的内容捕获到.(exec/match…)
let str = 'good good study , day day up!';
// => 学正则就是用来指定规则
let reg = /\d+/;
reg.test(str) // false

str = "2019-08-12"
console.log(reg.exec(str)); // [ '2019', index: 0, input: '2019-08-12', groups: undefined ]

1.0 编写正则表达式

正则表达式的创建方式有两种:

#1. 字面量方式创建正则 (两个斜杠之间包起来的,都是用来描述正则规则的元字符)
let reg = /\d+/;

#2. => 构造函数模式创建正则  两个参数: 1. 元字符字符串, 2.修饰符字符串
let reg2 = new RegExp("\\d+"); // 创造类实例的方式

1.1 正则表达式由两部分组成

正则表达式是由两部分组成的:

  1. 元字符
  2. 修饰符

1.1.1 元字符

// 常用元字符 几大类
# => 1. 量词元字符:  设置出现的次数
* 		零到多次
+ 		1 到多次
? 		0 次或 1{n} 	出现 n 次
{n,}	出现 n 到 多次
{n,m}	出现 n 到 m次

# =>2.	特殊元字符:  单个或者组合在一起, 代表特殊的含义
\		转义字符(普通字符转换为特殊字符, 或者特殊字符转化为普通字符)
.		除了 \n(换行符) 以外的任意字符
^		以哪一个元字符作为字符串开始
$		以哪一个元字符作为字符串结束
\n		换行符
\d		0~9 之间的数字
\D0~9 之间的任意字符.
\N		除换行符以外的任意字符
\w		数字,字母,下划线中的任意字符.
\s		一个空白字符(包含空格, 制表符, 换页符等)
\t		一个制表符(一个TAB)
\b		匹配一个单词的边界
x|y		x或者y中的一个字符
[xyz]	x或者y或者z中的任意 一个字符.
[^xy]	除了 x/y 以外的任意字符.
[a-z]	指定 a-z 这个范围中的任意字符.
[^a-z]	非 a-z的字符
()		正则中的分组符号
(?:)	只匹配不捕获
(?=)	正向预查
(?!)	负向预查

#3=> 普通元字符:  代表本身含义的.
/gene/ 此正则匹配的就是 "gene"

1.1.2 修饰符

正则表达式常用的修饰符:

// 正则表达式常用的修饰符: i m g
i(ignoreCase)	忽略单词大小写匹配
m(multiline)	可以进行多行匹配
g(global)		全局匹配

console.log(/g/.test('Gene')); // false
console.log(/g/i.test('Gene')); // true

2.0 正则表达式元字符详解

2.1 ^和$ 元字符详解

^ 字符在正则中的含义:

  1. 表示以 什么什么字符开始
  2. 取反, 表示 除了什么什么字符
#比如:
  /[^a-z\s]/  会匹配字符串 'good good study, day day up!' 中的 "," ,  该正则表示的是匹配  #(除了 a-z和空白字符的字符),因此这里匹配的是 逗号","
  const str = 'good good study, day day up!'
  const reg = /[^a-z\s]/
  console.log(str.search(reg));
  console.log(str[str.search(reg)]);

3**. 总结一下**

#1. 那什么时候 "^" 表示限定 以什么字符开头?
	    /[(^\s+)(\s+$)]/g	
		(^dog)$
		^(dog)$
#2. 什么时候 "^" 表示 否定?
		[^a]表示“匹配除了a的任意字符”。
		[^a-zA-Z0-9]表示“找到一个非字母也非数字的字符”。
#3. 总结:
		经过对比, 我们可以发现, 只要 "^" 这个字符是在中括号 "[]" 中直接使用的时候, 也就是说不包括嵌套分组使用的时候,  , 该字符才代表的是 # 对字符反向取非的意思.

^和$ 案例详解

let reg = /^\d/
console.log(reg.test("Gene")); // false
console.log(reg.test("2020-Gene")); // true
console.log(reg.test("Gene-2020")); // false
let reg = /\d$/
console.log(reg.test("Gene")); // false
console.log(reg.test("2020-Gene")); // false
console.log(reg.test("Gene-2020")); // true
#//=> 如果^和$ 两个都不加, 意思是只要字符串中 包含 符合规则的内容即可.
let reg = /\d+/
let reg = /\d+/
console.log(reg.test("Gene")); // false
console.log(reg.test("2020-Gene")); // true
console.log(reg.test("Gene-2020")); // true

#//=> ^和$两个都加: 字符串只能是和规则一致的内容.
let reg = /^\d+$/

2.2 \ 转义字符详解

\ 转义字符

// => .不是小数点, 是除 \n以外的任意字符
let reg = /^2.3$/
let reg = /^2.3$/
console.log(reg.test("2.3")); // true
console.log(reg.test("2@3")); // true
console.log(reg.test("23")); // false 2 和 3之间缺少字符,所以false

2.3 x|y 理解

x|y

let reg = /^18|29$/
console.log(reg.test('18')); // true
console.log(reg.test('29')); // true
console.log(reg.test('129')); // true
console.log(reg.test('189')); // true
console.log(reg.test('1829')); // true
console.log(reg.test('829')); // true
console.log(reg.test('182')); // true

// --->> 直接 x|y会存在很乱的优先级问题, 一般我们写的时候都伴随着 小括号进行分组, 因为小括号能改变处理的优先级
// 这样就只能是 18 或 29 中的一个了.
let reg = /^(18|29)$/
console.log(reg.test('18')); // true
console.log(reg.test('29')); // true
console.log(reg.test('129')); // false
console.log(reg.test('189')); // false
console.log(reg.test('1829')); // false
console.log(reg.test('829')); // false
console.log(reg.test('182')); // false

2.3 []

[]

  1. 中括号中出现的字符 一般都代表本身的含义
//1. 中括号中出现的字符 一般都代表本身的含义
let reg = /^[@+]+$/
console.log(reg.test('@')); // true
console.log(reg.test("@+")); // true

let reg2 = /^[@+]$/
console.log(reg2.test('@')); // true
console.log(reg2.test("+")); // true
console.log(reg2.test("@@")); // false

// ----------------------------
let reg = /^[\d]$/ // \d 在中括号中还是 0-9
console.log(reg.test('d')); // false
console.log(reg.test('\\')); // false
console.log(reg.test('0')); // true
  1. 中括号中不存在多位数
let reg = /^[18]$/
console.log(reg.test('1')); // true
console.log(reg.test('8')); // true
console.log(reg.test('18')); // false

3.0 常用正则表达式练习

3.1 验证是否为有效数字

/**
 * 规则分析
 * 	1.可能出现 + - 号, 也可能不出现
 *  2.如果只有一位0-9都可以, 多位数首位数不能为0
 *  3.小数部分可能有, 可能没有, 一旦有, 后面必须有小数点 + 数字
 * 
 */
let reg = /^[+-]?(\d|([1-9]\d+))(\.\d+)?$/

3.2 验证密码

// =>> 数字, 字母, 下划线.
// => 6-16位

let reg = /^[0-9a-zA-Z_]{6,16}$/
let reg = /^\w{6,16}$/

3.3 验证真实中文姓名的

中文汉字的区间范围 (\u4E00-\u9FA5)

/**
 *	1. 汉字 /^[\u4E00-\u9FA5]$/ // 中文汉字的区间范围
 *  2. 名字长度 2-10位
 *  3. 可能有译名  ·汉字 (·[\u4E00-\u9FA5]{2,10}{0,2})
 */
let reg = /^[\u4E00-u9FA5]{2,10}(·[\u4E00-\u9FA5]{2,10}){0,2}$/

"尼古拉斯·拉拉"
""

3.4 验证邮箱的正则表达式

let reg = /^\w+((-\w+)|(\.\w+))*@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/;

3.5 身份证号码正则

/**
 * 1. 一共 18 位
 * 2. 最后一位可能是大写 X, 大写X代表十
 * 
 * 身份证前6位: 省市县
 * 中间 8 位是出生年月日
 * 最后四位: 
 * 		最后一位是 X 或者数字
 *		倒数第二位  偶数 女 奇数 男
 *		其余的是经过算法算出来的
 */

#// ==> 小括号的第二个作用:  分组捕获, 不仅可以把大正则匹配的信息捕获到, 还可以捕获到每个小分组的内容
let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|X)$/;
// 捕获结果是一个数组, 包含每一个小分组单独获取的内容
const result = reg.exec("130435199302010071") 

4.0 两种创建正则表达式的区别.

#// ==>>> 构造函数因为传递的是字符串, \ 需要写两个才代表斜杠
let reg = /\d+/g;
let reg2 = new RegExp("\\d+","g")

#// ==>>> 正则表达式中的部分内容是变量存储的值
//1. 两个斜杠中间包起来的都是元字符(如果正则中要包含某个变变量的值, 则不能使用字面量方式创建, 只能用构造函数方式创建)
let type = "Gene"
let reg = new RegExp("^@"+type+"@$")
console.log(reg.test("@Gene@"));

5.0 正则表达式的捕获

实现正则捕获的办法

  • 正则RegExp.prototype 上的方法
    • exec 方法
    • test 方法
  • 字符串 String.prototype上支持正则表达式处理的办法
    • replace 方法
    • match 方法
    • splite 方法
    • … …

5.1 怎么实现正则捕获

基于 exec 实现正则的捕获

  1. 捕获到的结果是 null 或者是一个数组

    • 第一项是 本次捕获到的内容
    • 其余项: 对应小分组单独捕获的内容
    • index: 当前捕获到的结果在字符串中的索引起始位置
    • input 原始字符串.
  2. 每执行一次 exec 方法只能捕获到一个符合正则规则的值, 但是默认情况下, 我们执行多遍, 获取的结果永远是第一个匹配到的, 其余的捕获不到.

==>: 我们把这个特点叫做正则捕获的懒惰性, 默认只捕获第一个

let str = "Gene-2020yangyang2021qilai2022"
// 把 str 里的所有数字捕获
// =>> 实现正则捕获的前提是 当前正则要和字符串匹配, 如果不匹配, 捕获的结果是null
let reg = /\d+/;
console.log(reg.exec(str));

正则表达式捕获的懒惰性的原因是:

  • 默认情况下, lastIndex的值不会被修改, 每一次都是从字符串开始的位置查找, 所以找到的永远只是第一个
let str = "Gene-2020yangyang2021qilai2022"
let reg = /\d+/
/*
*  reg.lastIndex: 代表正则下一次匹配的其实索引位置, 默认是 0
* */
console.log(reg.lastIndex); // =>0 下面匹配捕获是从 str 索引为 零的位置开始查找.

怎么解决捕获的懒惰性?

设置全局匹配修饰符 g后, 第一次匹配完, lastIndex 的值会自动修改.

let reg = /\d+/g;  // g 全局匹配来解决他的懒惰性.
let str = "Gene-2020yangyang2021qilai2022"
console.log(reg.exec(str));
console.log(reg.lastIndex); // 9 lastIndex 发生了改变.
console.log(reg.exec(str));

// 当捕获到最后一次, 再次捕获的时候, 再次捕获的结果是 null, 但是 lastIndex 又初始为0, 再次捕获又从第一个开始捕获.

# -------------------------------------------------
# 注意:
#	如果加了全局验证符 g 后, reg.test(str) 匹配验证后, 也会改变 lastIndex 的值.
let reg = /\d+/
if(reg.test(str)){ # // 注意, 这种写法会出现错误, 
	console.log(reg.lastIndex) // 此处lastIndex 已经被修改了一次
    console.log(reg.exec(str)) // 导致此处捕获不再从 0 开始, 就导致你捕获的内容不再从第一项开始
}

#===>>需求: 编写一个方法 execAll, 执行一次可以把所有匹配的结果捕获到(前提正则一定要设置全局修饰符 g)
~function(){
   function execAll(str = ""){
       // => str: 要匹配的字符串
       // => this: RegExp 的实例(当前操作的正则)
       // 进来后的第一件事, 是验证当前正则是否设置了 g, 不设置就不能再进行循环捕获, 否则会导致死循环.
       if(!this.global){
           return this.exec(str) // 如果没加 g, 则只返回第一个匹配的值.
       }
       let arr = []; // 用来存储每次捕获的结果
       let res = this.exec(str) // => res 存储每一次捕获的内容
       while(res){
           arr.push(res[0])
         // 只要捕获的内不为 null, 则继续捕获下去
           res = this.exec(str)
       }
       return arr.length === 0 ? null : arr;
   } 
    RegExp.prototype.execAll = execAll;
}()

let reg = /\d+/g;
console.log(reg.execAll(str))

5.2 捕获所有匹配结果

字符串里有一个方法可以匹配所有结果:

# 字符串里有一个方法可以匹配所有结果, 结果会同我们写的 execAll() 方法效果一样
# 字符串里有一个 match() 方法, 可以实现匹配并返回所有匹配结果, 但是前提也得设置全局匹配 g
let reg = /\d+/g
console.log("Gene2020@2021StudyReg2022".match(reg)) // [ '2020', '2021', '2022' ]

6.0 正则表达式的分组捕获和分组引用

// =>> 身份证号码
let str = "130435199302011356";
let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(?:\d|X)$/
console.log(reg.exec(str))
console.log(str.match(reg))

// => 获取的结果都是数组
	["130435199302011356", "130435", "1993", "02", "01", "5", "6", index: 0, input: "130435199302011356"]

// => 第一项: 大正则匹配的结果
// => 其余项: 每一个小分组单独匹配捕获的结果.
// => 如果设置了分组(改变优先级), 但是捕获的时候不需要单独捕获, 可以基于 ?: 来处理.
// ==>> 既要捕获到 {数字}, 也想把单独的数字也捕获到, 例如第一次找到 {0}, 还需要单独获取 0
let str = "{0}年{1}月{2}日";
// 不设置 g 值匹配一次, exec 和 match 获取的结果一致
let reg = /\{(\d+)\}/
console.log(reg.exec(str))
console.log(str.match(reg))

# ---->> 匹配多次
let str = "{0}年{1}月{2}日";
let reg = /\{(\d+)\}/g
console.log(reg.exec(str))
console.log(str.match(reg))

// 多次匹配的情况下, match 只能把大正则匹配的内容获取到, 小分组匹配的信息无法获取.

# ---->>> 解决办法, 手动循环解决.
let arrBig = []
let arrSmall = []
let res=reg.exec(str)
while(res){
  let [big,small] = res
  arrBig.push(big)
  arrSmall.push(small)
  res = reg.exec(str)
}
#----------> 分组的第三个作用:  '分组引用'
let str = "meet" // moon, look, foot etc...
let reg = /^[a-zA-Z]([a-zA-Z])\1[a-zA-Z]$/

// ===> 分组引用, 就是通过 "\数字" 让其代表 和对应 的分组出现一模一样的内容.
// 上述 \1 就表示小括号引用内,[a-zA-Z]出现一次后, 后边 \1 出现的和前边儿的一模一样的值.

console.log(reg.test("book")); // true
console.log(reg.test("foot")); // true
console.log(reg.test("some")); // false

7.0 正则表达式捕获的贪婪性

正则捕获的贪婪性:

默认情况下, 正则捕获的时候, 是按照当前正则所匹配的最长结果来获取的.

let str = "Gene2020@2021学习"
let reg = /\d+/g
console.log(str.match(reg)); // => [ '2020', '2021' ]

# --- 取消正则表达式捕获时候的贪婪性, 在量词元字符后面设置 ? 来取消捕获时候的贪婪性.(按照正则匹配的最短结果来获取).
let str = "Gene2020@2021学习"
let reg = /\d+?/g           #// 取消正则捕获的贪婪性
console.log(str.match(reg)); // [ '2', '0', '2', '0', '2', '0', '2', '1' ]

7.1 正则中问号 ? 的五大作用

  • 问号左边是非量词元字符: 那它本身就是量词元字符, 出现零到一次
  • 问号左边是量词元字符: 取消捕获时候的贪婪性
  • (?:)小括号里问号加冒号, 表示不捕获
  • (?=) 小括号里问号加等号, 表示正向预查
  • (?!) 小括号里问号加叹号, 表示负向预查

8.0 正则表达式捕获的其他办法.

8.1 test捕获

基本上不用, 但是也能捕获. test本意是匹配.

let str = "{0}年{1}月{2}日"
let reg = /\{(\d+)\}/g
console.log(reg.test(str)) // => true
console.log(RegExp.$1) // => "0" // 捕获了第一个

console.log(reg.test(str)) // => 再匹配一次 true
console.log(RegExp.$1) // => "1" // 捕获了第二个

console.log(reg.test(str)) // => 再匹配一次 true
console.log(RegExp.$1) // => "2" // 捕获了第三个

console.log(reg.test(str)) // => 再匹配一次 false
console.log(RegExp.$1) // => "2" // 存储的是上一次捕获的结果.

# //=> RegExp.$1~RegExp$9:  获取当前本次正则匹配后, 第一个到第九个分组的信息.
// 可以通过 console.dir(RegExp) 来查看

8.2 replace 捕获

replace 字符串中实现替换的方法 (一般都是伴随正则一起使用的)

let str = "Gene@2020|Gene@2021";
# // 1. 不用正则, 执行一次, 只能替换一个.

// 把 Gene 都替换成 汉字 "杨"
str = str.replace("Gene","杨")
console.log(str)

str = str.replace("Gene","杨")
console.log(str)

# //2. 用正则来实现就可以一次性全局匹配.
str = str.replace(/Gene/g,"杨")
console.log(str)
# 需求 => 把 Gene 替换成 "GeneStudy"
let str = "Gene@2020|Gene@2021";
str = str.replace("Gene","GeneStudy").replace("Gene","GeneStudy")
console.log(str); // GeneStudyStudy@2020|Gene@2021     | 叠词替换, 容易出现了错误, 因为 replace 每次替换是从 0 索引开始的.(类似于正则捕获的懒惰性)

# =====> 基于正则加全局匹配 g 来实现
str = str.replace(/Gene/g,"GeneStudy")

应用案例:

把时间字符串进行处理:

# 需求: 变为 "2020年06月02日"
let time = "2020-06-02"
let reg = /^(\d{4})-(\d{1,2})-(\d{1,2})$/g

// 这样可以实现
time = time.replace(reg,"$1年$2月$3日")
console.log(time)

# ------------------------------------------
// 原理步骤 还可以这样处理 [str].replace([reg],[function])
// 1. 首先拿 reg 和 time 进行匹配, 能匹配几次, 就会把传递的函数执行几次.(而且是匹配一次就执行一次.)
// 2.不仅把方法执行了, 而且 replace 还给方法传递了实参信息.(和 exec 捕获的内容一致的信息: 大正则匹配的内容, 小分组匹配的信息, ...).
// 3.在回调函数中, 我们return返回的是啥, 就把当前匹配的内容替换成啥.

let reg = /^(\d{4})-(\d{1,2})-(\d{1,2})$/g
time = time.replace(reg,(big,$1,$2,$3)=>{
    // => 这里的 $1~$3 是我们自己设置的变量
    console.log(big,$1,$2,$3)
    return `${$1}${$2}${$3}日`
})
console.log(time);

单词首字母大写:

let str = "good good study, day day up!";
let reg = /\b([a-zA-Z])[a-zA-Z]*\b/g  // /\b([a-zA-Z])[a-zA-Z]*\b/g 代表单词边界.

// 函数执行了六次, 每次都把正则匹配信息传递给函数
// 每一次 arg: ["good","g"] ["good","g"] ["study","s"]
str = str.replace(reg, (...arg) => {
    let [content, $1] = arg
    $1 = $1.toUpperCase()
    content = content.substring(1)
    return $1 + content
})
console.log(str)

单词首字母大写:

let str = "good good study, day day up!";
let reg = /\b([a-zA-Z])[a-zA-Z]*\b/g  // /\b([a-zA-Z])[a-zA-Z]*\b/g 代表单词边界.

// 函数执行了六次, 每次都把正则匹配信息传递给函数
// 每一次 arg: ["good","g"] ["good","g"] ["study","s"]
str = str.replace(reg, (...arg) => {
    let [content, $1] = arg
    $1 = $1.toUpperCase()
    content = content.substring(1)
    return $1 + content
})
console.log(str)

你可能感兴趣的:(JavaScript,Java,正则表达式)