正则表达式(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. 字面量方式创建正则 (两个斜杠之间包起来的,都是用来描述正则规则的元字符) let reg = /\d+/; #2. => 构造函数模式创建正则 两个参数: 1. 元字符字符串, 2.修饰符字符串 let reg2 = new RegExp("\\d+"); // 创造类实例的方式
正则表达式是由两部分组成的:
- 元字符
- 修饰符
// 常用元字符 几大类 # => 1. 量词元字符: 设置出现的次数 * 零到多次 + 1 到多次 ? 0 次或 1次 {n} 出现 n 次 {n,} 出现 n 到 多次 {n,m} 出现 n 到 m次 # =>2. 特殊元字符: 单个或者组合在一起, 代表特殊的含义 \ 转义字符(普通字符转换为特殊字符, 或者特殊字符转化为普通字符) . 除了 \n(换行符) 以外的任意字符 ^ 以哪一个元字符作为字符串开始 $ 以哪一个元字符作为字符串结束 \n 换行符 \d 0~9 之间的数字 \D 非 0~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"
正则表达式常用的修饰符:
// 正则表达式常用的修饰符: i m g i(ignoreCase) 忽略单词大小写匹配 m(multiline) 可以进行多行匹配 g(global) 全局匹配 console.log(/g/.test('Gene')); // false console.log(/g/i.test('Gene')); // true
^
字符在正则中的含义:
- 表示以 什么什么字符开始
- 取反, 表示 除了什么什么字符
#比如: /[^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+$/
\
转义字符详解
\
转义字符// => .不是小数点, 是除 \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
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
[]
[]
- 中括号中出现的字符 一般都代表本身的含义
//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
- 中括号中不存在多位数
let reg = /^[18]$/ console.log(reg.test('1')); // true console.log(reg.test('8')); // true console.log(reg.test('18')); // false
/**
* 规则分析
* 1.可能出现 + - 号, 也可能不出现
* 2.如果只有一位0-9都可以, 多位数首位数不能为0
* 3.小数部分可能有, 可能没有, 一旦有, 后面必须有小数点 + 数字
*
*/
let reg = /^[+-]?(\d|([1-9]\d+))(\.\d+)?$/
// =>> 数字, 字母, 下划线.
// => 6-16位
let reg = /^[0-9a-zA-Z_]{6,16}$/
let reg = /^\w{6,16}$/
中文汉字的区间范围 (
\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}$/
"尼古拉斯·拉拉"
""
let reg = /^\w+((-\w+)|(\.\w+))*@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/;
/**
* 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")
#// ==>>> 构造函数因为传递的是字符串, \ 需要写两个才代表斜杠 let reg = /\d+/g; let reg2 = new RegExp("\\d+","g") #// ==>>> 正则表达式中的部分内容是变量存储的值 //1. 两个斜杠中间包起来的都是元字符(如果正则中要包含某个变变量的值, 则不能使用字面量方式创建, 只能用构造函数方式创建) let type = "Gene" let reg = new RegExp("^@"+type+"@$") console.log(reg.test("@Gene@"));
实现正则捕获的办法
- 正则
RegExp.prototype
上的方法
exec
方法test
方法- 字符串
String.prototype
上支持正则表达式处理的办法
replace
方法match
方法splite
方法- … …
基于
exec
实现正则的捕获
捕获到的结果是 null 或者是一个数组
- 第一项是 本次捕获到的内容
- 其余项: 对应小分组单独捕获的内容
index
: 当前捕获到的结果在字符串中的索引起始位置input
原始字符串.每执行一次
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))
字符串里有一个方法可以匹配所有结果:
# 字符串里有一个方法可以匹配所有结果, 结果会同我们写的 execAll() 方法效果一样 # 字符串里有一个 match() 方法, 可以实现匹配并返回所有匹配结果, 但是前提也得设置全局匹配 g let reg = /\d+/g console.log("Gene2020@2021StudyReg2022".match(reg)) // [ '2020', '2021', '2022' ]
// =>> 身份证号码 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
正则捕获的贪婪性:
默认情况下, 正则捕获的时候, 是按照当前正则所匹配的最长结果来获取的.
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' ]
基本上不用, 但是也能捕获.
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) 来查看
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)