文章目录
问题:
^ 符号,只有在 中括号里才算是取反吗?
JS正则表达式
正则表达式: regular express:(正则类: RegExp)
正则表达式是用来处理字符串的规则.
正则表达式, 只能处理字符串.
正则表达式是一个规则:
- 可以验证字符串是否符合某个规则(test),
- 也可以把字符串中符合规则的内容 捕获到 (exec/match)
# test 匹配方法, 返回 布尔值 let str = "good good study, day day up!"; // => 学正则, 就是用来制定规则. let reg = /\d+/; // 是否包含数字 reg.test(str) // false # exec 捕获方法. str = "2021-07-10" reg.exec(str) // reg.js:7 ["2021", index: 0, input: "2021-07-10", groups: undefined]
编写正则表达式
正则表达式的创建方式有两种
- 字面量创建方式
- 构造函数模式创建(两个参数: 1. 元字符字符串, 2. 修饰符字符串)
//=> 字面量创建方式 (两个斜杠之间包起来的,都是用来描述规则的元字符) let reg1=/\d+/; //=> 构造函数创建. 两个参数: 元字符字符串(需要转义), 修饰符字符串 let reg2 = new RegExp("\\d+");
正则表达式 由 两部分组成
- 元字符
- 修饰符
我们要学好正则表达式, 就需要把这些元字符和修饰符背下来
元字符:
**元字符的分类包括 **
- 量词元字符
- 特殊元字符
- 普通元字符
量词元字符
#1. 量词元字符: 设置出现的次数. js 中常用的量词元字符有 6 个 * 零到多次 + 一到多次 ? 零次或一次 {n} 出现n次 {n,} 出现 n 到多次 {n,m} 出现 n 到 m 次
特殊元字符
#2. 特殊元字符: 单个或组合在一起代表特殊的含义 \ 转义字符: 1. 把普通字符转换成有特殊含义的 2. 把特殊含义转换为普通字符 . 除 \n(换行符) 以外的任意字符 ^ 以哪一个元字符作为开始. $ 以哪一个元字符作为结束 # -------------------------------------------- \n 换行符 \N 除换行符以外的任意字符(即 和 . 相同) \d 0-9 之间的数字 \D 非 0-9 之间的数字 \w 数字, 字母, 下划线中的任意一个字符 \W 非 数字, 字母, 下划线中的任意一个字符 \s 一个空白字符(包含, 空格, 制表符, 换页符) \S \t 一个制表符(一个 TAB键盘: 四个空格) \b 匹配一个单词的边界 # ---------------------------------------------- x|y x 或者 y 中的一个字符 1|9|5 [xyz] x或者y,或者 z 中的一个字符[adghve22] 取其中一个字符 [^xy] 除了 x 或y以外的任意字符 [a-z] 指定 a-z 这个范围中的任意字符[0-9a-zA-Z] === \w [^a-z] 取反. 除了 a-z以外的任意字符 # 分组 =-========================================= () 正则中的分组符号 (?:) 只匹配不捕获 (?=) 正向预查 (?!) 负向预查
普通元字符
# ================================================= #3. 普通元字符: 代表本身含义的字符 /gene/ 正则匹配的就是 "gene" 本身含义
修饰符
正则表达式中常用的修饰符:
# 正则表达式中常用的修饰符 img i ignoreCase 忽略单词大小写匹配 m multiline 忽略多行匹配, 可以进行多行匹配 g global 全局匹配
元字符详解
$ 和 ^
^ 符号, 在 [] 中括号中是取反的意思,如下表示:
/[^?=&#]/g; // 表示除了 ? = & # 符号之外的任意符号.
$ ^
//let reg = /^a/; // 以字母 a 开头 let reg = /^\d/; // 以数字开始 reg.test("gene"); reg.test("2021gene"); reg.test("gene2021")
let reg = /\d$/; // 以数字结束 reg.test("gene"); reg.test("2021gene"); reg.test("gene2021")
let reg = /\d+/; // 两个都不加, 字符串中, 包含符合规则的内容即可 let reg2 = /$\d$/ // 两个都加, 只能是和规则一致的内容. #// 举个例子: 验证手机号码(11位, 第一个数字是 1 即可) let reg = /^1\d{10}$/
\ 斜杠
转义字符:
- 把特殊符号,转化为普通符号,
- 或 把普通符号转化为特殊符号
注意: 字符串当中的一个斜杠, 也有特殊含义:
//=> . 不是小数点, 是除 \n 以外的任意字符. let reg = /^2.3$/; console.log(reg.test("2.3")) // true console.log(reg.test("2@3")) // true console.log(reg.test("23")) // fals 中间的点, 得占一个字符. 即匹配除 \n 换行符以外的任意一个字符. #---------------------------------- // 基于转义字符, 让其只能表示小数点 reg = /^2\.3$/; console.log(reg.test("2.3")) // true console.log(reg.test("2@3")) // false
let str = "\\d"; // 字符串里的 \ 斜杠, 也有转义的作用, 所以也需要做转义 let reg = /^\\d$/ reg.test(str) // true
x|y 规则
x|y 规则
直接 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 # ======== 分组 ================== 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
[] 规则
中括号中出现的字符, 大部分都表示本身含义.
- 中括号中出现的字符, 一般都代表本身的含义
- 中括号中, 不存在多位数
#// 1. 中括号中出现的字符, 一般都代表本身的含义. let reg = /^[@+]+$/; // 这里中括号中的 + 号代表的就是 加 号本身 console.log(reg.test("@@")) console.log(reg.test("@+")) reg=/^[\d]$/ # //=> \d 在中括号中还是 0-9 console.log(reg.test("d")) console.log(reg.test("\\")) console.log(reg.test("9"))
# 中括号中不存在多位数 let reg = /^[18]$/ console.log(reg.test("1")) console.log(reg.test("8")) console.log(reg.test("18")) // false reg=/^[10-29]$/; //=> 1 或者0-2或者9 console.log(reg.test("1")) console.log(reg.test("9")) console.log(reg.test("0")) console.log(reg.test("2")) console.log(reg.test("10")) // false
常用正则表达式
验证是否为有效数字
/** *规则分析 * 1. 可能出现 + - 号, 也可能不出现 [+-]? * 2. 一位 0-9 都可以, 多位数 首位不能为 0 (\d|([1-9]\d+)) * 3. 小数部分可能有, 可能没有, 一旦有, 后面必须有小数点 加 数字 */ let reg = /^[+-]?(\d|([1-9]\d+))(\.\d+)?$/
// 匹配 十 到 二十 中间的数, 就用到了分组 let reg = /(10|11|12|13|14|15|16|17|18|19|20)?/ console.log(reg.test("10"))
验证密码
// => 数字字母,下划线 // 6~16位数. let reg = /^\w{6,16}$/;
验证真实姓名的
/** * 规则: * 1. 汉字规则: /^[\u4E00-\u9FA5]$/ * "奥斯特洛夫斯基·拖拉机副司机" * 2. 名字长度 2-10 位 * 3.可能有译名 */ let reg = /^[\u4E00-\u9FA5]{2,10}(·[\u4E00-\u9FA5]{2,10}){0,2}$/;
验证邮箱的
比较完善的邮箱规则, 开发中可以直接使用
// 邮箱的名字可以由数字字母下划线, - . 几部分组成. 但是 - 和 . 不能连续出现, 也不能作为开始 let reg = /^\w+((-\w+)|(\.\w+))*@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/; //=> \w+((-\w+)|(\.\w+))* 1. 开头是 数字字母下划线 (1到多次) 2. -数字字母下划线 或者 .数字字母下划线 (整体0-多次) //=> @[A-Za-z0-9]+ 1. @后面紧跟着数字字母, 可以使一到多位 //=> ((\.|-)[A-Za-z0-9]+)* // 对 @ 符后面的名字的补充 1. .和数字字下划线出现 0 到 多次 或者 -和数字字母下划线出现0到多次
身份证号码
/** * 身份证号码规则 * 1. 一共18位 * 2. 最后一位可能是 X (大写X代表十) * * 3. 身份证前六位: 省市县 130435 * 4. 中间八位: 年月日 * * 最后四位: * 6. 最后一位是 X 或者数字 * 7. 倒数第二位 偶数是 女 奇数是男 * 其余的是经过算法算出来的 */ let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|X)$/; reg.exec("130425199308090081"); // ["130425199308090081", "130425", "1993", "08", "09", "8", "1", index: 0, input: "130425199308090081", groups: undefined] // 捕获的结果是一个数组, 包含每一个小分组单独获取的内容.
两种创建正则表达式的区别
- 构造函数因为传递的是字符串, \ 需要写两个才代表斜杠.
- 如果正则中要包含某个变量是的值, 则不能使用 字面量方式创建. 只能使用构造函数方式. (因为它传递的规则是字符串, 只有这样才能进行字符串拼接).
//=> 构造函数是因为传递的是字符串, \ 需要写两个才代表斜杠. let reg = /\d+/g; reg = new RegExp("\\d+","g") //=> 正则表达式中的 部分内容, 是变量存储的值. // 1.两个斜杠之间包起来的都是元字符. // 2. 字面量创建正则的方式, 不能再中间加变量 let type = "gene"; let reg=/^@"+type+"@$/ console.log(reg.test("@gene@")) // false let reg = new RegExp("^@"+type+"@$","g") console.log(reg.test("@gene@")) // true
正则的捕获方法
/** 正则捕获的懒惰性原因. * 懒惰性的原因就是: 默认情况下, lastIndex 的值是不会被修改的, 每一次都是从字符串开始位置查找, 所以找到的永远都只是第一个. * let reg = /\d/ * reg.lastIndex: 当前正则下一次匹配的起始索引位置. * 第一次匹配捕获完成, lastIndex没有改变, 所以下一次 exec 依然是从 字符串 0 的位置开始捕获, 找到的永远是 第一个匹配到的. */
正则捕获的特点
正则捕获的特点:
- 正则捕获的懒惰性
- 正则捕获的贪婪性
正则捕获的懒惰性
正则捕获的贪婪性
- 正则捕获的贪婪性: 默认情况下, 正则捕获的时候, 是按照当前正则所匹配的最长结果来捕获的. 如下面正则捕获的结果
["2021", "2022"]
, 每次捕获都是捕获了最多的条件2021
和2022
, 而不会单独捕获2
0
2
这样单个捕获.let str = "Gene2021@2022study" let reg = /\d+/g; console.log(str.match(reg)); //=> ["2021", "2022"] #// 取消正则匹配的贪婪性. 在量词元字符后面设置 ?号, 取消捕获时候的贪婪性. 按照正则匹配内容的最短结果来获取. let str = "Gene2021@2022study" let reg = /\d+?/g; console.log(str.match(reg)); //=> ["2", "0", "2", "1", "2", "0", "2", "2"]
正则捕获的方法:
- 正则里面的方法: RegExp.prototype 上的方法
- exec() 方法
- test() 方法
- 字符串 String.prototype 上支持正则表达式处理的方法.
- replace() 方法
- match() 方法
- splite() 方法
- … …
exec 捕获
exec() 捕获是正则上的方法:
基于 exec 实现正则的捕获
捕获到的结果是 null 或者 是一个数组.
第一项: 是 本次捕获到的内容.
其余项: 对应小分组单独捕获的内容
index: 当前捕获到的结果在字符串中起始索引.
input: 原始字符串.
每执行一次 exec, 只能捕获到一个符合正则规则的内容, 但是默认情况下, 我们 执行 一百遍, 获取的结果永远都是第一个匹配到的, 其余的捕获不到. (正则捕获的懒惰性: 默认只捕获第一个)
let str = "Gene2021change2022life2023"; //=> 实现正则捕获的前提是: 当前正则要和字符串匹配. 如果不匹配, 捕获的结果是 null. // 1. 把 str 中的所有的数字都匹配 let str = "Gene2021change2022life2023"; let reg = /\d+/; console.log(reg.exec(str)) // 捕获的结果是一个数组: ["2021", index: 4, input: "Gene2021change2022life2023", groups: undefined] # ----- 修饰符 ------------------------ // g 修饰符, 设置全局匹配符 g 后, 第一次匹配完, lastIndex 会自动修改. let str = "Gene2021change2022life2023"; let reg = /\d+/g;
注意:
# 注意: let reg = /\d+/g; if(reg.test(str)){ console.log(lastIndex); // 基于 test 匹配验证之后, lastIndex 已经被修改为第一次匹配后的索引结果, 所以下一次捕获不会再从头开始捕获了. console.log(reg.exec(str)); }
实现一个 execAll 捕获所有的方法
🚩 需求: 编写一个 方法 execAll, 执行一次可以把所有匹配的结果捕获到.(前提: 正则一定要设置全局修饰符)
function execAll(str=""){ // str: 要匹配的源字符串 // this: RegExp 的实例(当前操作的正则) // arr 存储所有捕获的信息, res 存储每一次捕获的内容. // 进来之后的第一件事, 是验证正则是否设置了 g, 不设置则不能进行循环捕获, 否则会导致死循环. if(!this.global) return this.exec(str) let arr = [],res=this.exec(str); while(res){ // 只要捕获的内容不为 null, 则继续捕获 arr.push(res[0]) // 把每一次捕获的内容存到 arr数组中 res = this.exec(str) } return arr; } RegExp.prototype.execAll = execAll let str = "Gene2021change2022life2023"; let reg = /\d+/g;
match 方法捕获
字符串中的match方法, 可以在执行一次的情况下, 捕获到所有匹配的数据(前提 正则也是全局匹配g才可以)
let str = "Gene2021change2022life2023"; let reg = /\d+/g; console.log(str.match(reg))
test方法捕获
- test捕获方法. 基本不怎么用. test 本意识匹配
let str = "{2021}年{07}月{10}日"; let reg = /\{(\d+)\}/g; console.log(reg.test(str)) // true console.log(RegExp.$1) // 2021 console.log(reg.test(str)) // true console.log(RegExp.$1) // 07 console.log(reg.test(str)) // true console.log(RegExp.$1) // 10 console.log(reg.test(str)) // false console.log(RegExp.$1) // 10 存储的是上一次捕获的结果 // RegExp.$1 ~ RegExp.$9; 获取当前本次匹配正则后, 第一个到第九个分组的信息.
replace 方法捕获 很重要
replace 字符串中实现替换的方法.(一般都是伴随正则一起的)
let str = "gene@2021|gene@2022" //=> 把字符串 "gene" 都替换成 "杨阳" #//1. 不用正则,执行一次只能替换一个. str=str.replace("gene","杨阳"); console.log(str) // "杨阳@2021|gene@2022" 也是默认只替换一次. #//2. 使用正则会简单一点 let str = "gene@2021|gene@2022" str=str.replace(/gene/g,"杨阳"); console.log(str) // 杨阳@2021|杨阳@2022
需求
# replace 不用正则解决不了的问题. let str = "gene@2021|gene@2022" // 把 "gene" 替换成 "geneYang" str=str.replace("gene","geneYang").replace("gene","geneYang") console.log(str) // geneYangYang@2021|gene@2022 出现了问题. 每次都从第一个位置开始找. (类似于正则捕获的懒惰性)
# replace 使用正则解决问题 let str = "gene@2021|gene@2022" // 把 "gene" 替换成 "geneYang" str=str.replace(/gene/g,"geneYang") console.log(str) // geneYang@2021|geneYang@2022 替换正确.
案例:
把时间字符串进行处理
let time = "2021-07-10"; //=> 变为 "2019年08月13日" let reg=/^(\d{4})-(\d{1,2})-(\d{1,2})$/; # //=> 这样可以实现 // time = time.replace(reg,"$1年$2月$3日") // console.log(time) // 2021年07月10日 # ================================== #// [str].replace([reg],[function]) // 1. 首先 那 reg 和 time 进行匹配捕获, 能匹配几次就会把传递的函数执行几次.(而且是匹配一次就执行一次) //2. 不仅把方法执行, 而且 replace 还给方法传递了实参信息.(和exec捕获内容一致的信息:大正则匹配的内容, 小分组匹配的信息... ...) // 3. 函数中 return 返回的内容,就是整个字符串替换后的内容. /* let time = "2021-07-10"; let reg=/^(\d{4})-(\d{1,2})-(\d{1,2})$/; time=time.replace(reg,(big,$1,$2,$3)=>{ // 这里的 $1 - $3是我们自己设置的变量 console.log(big,$1,$2,$3); }) // output: 2021-07-10 2021 07 10 */ let time = "2021-07-10"; let reg=/^(\d{4})-(\d{1,2})-(\d{1,2})$/; time=time.replace(reg,(big,...arg)=>{ let [$1,$2,$3] = arg; $2.length<2?$2="0"+$2:null; $2.length<2?$3="0"+$3:null; return $1+"年"+$2+"月"+$3+"日" })
案例:
验证一个字符串中哪个字母出现的次数最多, 出现多少次?
let str = "good good study, day day up hahahaha"; let obj={}; [].forEach.call(str,char=>{ if(char in obj){ obj[char]++; return } obj[char]=1; }) console.log(obj)
正则表达式之时间字符串格式化
//=> 服务器获取的时间字符串的格式 // "2021-07-10 18:34:3" // "2021/0710 18:34:3" //=> 想要转变的格式 // "07月10日 18时34分" // "2021年07月10日" // 时间字符串格式化处理. !function(){ /** * formatTime: 时间字符串的格式化处理 */ function formatTime(){ //1. 首先获取时间字符串中的, 年月日等信息 let timeAry = this.match(/\d+/g); console.log(timeAry) let template = "{0}年{1}月{2}日 {3}时{4}分{5}秒" template=template.replace(/\{(\d+)\}/g,(content,$1)=>{ // content: 当前匹配大正则匹配的信息, $1 本次单独匹配小分组匹配的信息 let time=timeAry[$1] || "00"; time.length<2 ? time="0"+time:null; }); return template; } /* 扩展到内置类 String.prototype上 */ // 这样的写法可以往原型上扩展多个方法,只需要在数组里添加字符串就可以了. ["formatTime"].forEach((item)=>{ String.prototype[item]=eval(item) }) }();
? 号在正则中的五大作用
- 问号左边是 非 量词元字符:本身代表量词, 表示出现 0 到 一次.
- 问号左边是量词元字符: 取消捕获时候的贪婪性
- (?: ) 只匹配不捕获.
- (?=) 正向预查
- (?!) 负向预查
分组的作用
- 分组的第一个作用: 改变优先级
- 分组的第二个作用: 分组捕获.
- 不仅可以把大正则匹配的信息捕获到,
- 还可以单独捕获到每个小分组的内容.