一、学习正则

regular expression: RegExp 用来处理字符串的规则

  • 只能处理字符串
  • 他是一个规则:用来验证字符串是否符合某个规则,也可以把字符串符合规则的内容捕获到(exec/match…)

\d 0-9 任何一个数字
+ 匹配一到多个

1、匹配(test)

2、捕获(exec\match)

  1. let str = "good good study, day day up!";
  2. let reg = /\d+/;
  3. reg.test(str) //=> false
  4. str = "2019-08-12"
  5. reg.exec(str); //=> ['2019',index:0,inputs:'原始字符串']

二、编写正则表达式

1、创建的方式

  • 字面量方式(两个斜杠之间包起来的,都是用来描述规则的元字符) let reg1 = /d+/
  • 构造函数模式创建 两个参数 : 元字符字符串,修饰符字符串 let reg2 = new RegExp(“\d+”);

2、正则表达式由两部分组成

  • 元字符
  • 修饰符

3、常用的元字符

3-1 量词元字符 设置出现的次数
* 代表 零到多次
+ 代表 一到多次
? 零次或者一次
{n} 出现n次
{n,} 出现n到多次
{n,m} 出现n到m次 包含n和m

3-2 特殊元字符:单个或者组合在一起代表特殊的含义
\ 转义字符(普通->特殊->普通)
. 除\n(换行符)以外的任意字符
^ 以哪一个元字符作为开始
$ 以哪一个元字符作为结束
\n 换行符
\d 0~9之间的一个数字
\D 非0~9之间的一个数字
(大写和小写的意思是相反的)
\w 数字、字母、下划线中的任意一个字符
\s 一个空白字符(包含空格、制表符、换页符等)
\t 一个制表符(一个TAB键:四个空格)
\b 匹配一个单词的边界
x|y x或者y中的一个字符
[xyz] x或者y或者z中的一个字符
[^xy] 除了x/y以外的字符
[a-z] 指定a-z这个范围中的任意字符
[0-9a-zA-Z_] === \w
[^a-z] 上一个取反
( ) 正则中的分组符号
(?:) 只匹配不捕获
(?=) 正向预查
(?!) 负向预查
\1 前后相同匹配 o\1o 相同的两个o

3-3 普通元字符,代表本身含义
/xiaohua/ 此正则匹配的就是’xiaohua’

4、常用的修饰符 i 、 m 、 g

| i |

忽略单词大小写匹配

| | —- | —- | | m |

可以进行多次匹配

| | g |

global 全局匹配

|

  1. /A/.test('lala') => false
  2. /A/i.test('lala') => true

三、元字符详细解析

1、开头结尾 ^ $

  1. ^ 匹配以数字开头
  2. let reg = /^\d/;
  3. reg.test("xiaohua")
  4. reg.test("xiaohua2019")
  5. reg.test("2019xiaohua")
  6. $ 匹配以数字结尾
  7. let reg = /\d$/;
  8. reg.test("xiaohua")
  9. reg.test("xiaohua2019")
  10. reg.test("2019xiaohua")
  11. 都不加包含即可
  12. let reg = /\d/;
  13. reg.test("xiaohua")
  14. reg.test("xiaohua2019")
  15. reg.test("2019xiaohua")
  16. 两个都加 只能是和规则一致的内容
  17. 例子:验证手机号码
  18. let reg = /^1\d{10}$/

2、转义字符 \

  1. 只能匹配2.3
  2. let reg = /^2.3$/;
  3. reg.test('2.3') true
  4. reg.test('2@3') true
  5. reg.test('23') false
  6. 原因是.此时代表除\n以外的任意字符,导致匹配2@3也能匹配上,解决方法就是加上\进行转义
  7. let reg = /^2\.3$/;
  8. reg.test('2.3') true
  9. reg.test('2@3') false
  10. reg.test('23') false
  11. let reg=/^\d$/
  12. reg.test('\d') false
  13. let reg=/^\\d$/ 把特殊意思符号转成普通,字符串中\\才代表\
  14. reg.test('\\d') true
  15. let reg=/^\\$/ 字符串中\\才代表\
  16. reg.test('\\') true

3、 x | y 、 分组()

小括号不仅可以把正则匹配信息捕获到,还可以单独捕获每个小分组的内容 exec

  1. let reg = /^18|29$/;
  2. reg.test("18")
  3. reg.test("29")
  4. reg.test("129")
  5. reg.test("189")
  6. reg.test("1829")
  7. 上面全是true
  8. 直接x|y会存在很乱的优先级问题,一般我们都伴随着小括号进行分组,因为小括号改变处理的优先级
  9. reg = /^(18)|(29)$/ 只能是1829中的一个了
  10. reg.test("18") true

4、 []

    1. 中括号中出现的字符一般都代表本身的含义, ```javascript 下面的@+ +就是正常的+ let reg = /^[@+]+$/ //=> 代表@或者+出现一次到多次 reg.test(“@@”) reg.test(“++”)

注意:\d 在[]中代表的还是数字的意思,并不是本身意思 let reg = /^[\d]$/ reg.test(“d”) //=> false reg.test(“12”) //=> true

let reg = /^[\d]$/ reg.test(“\d”) //=> true reg.test(“12”) //=> false

  1. - 2. 中括号中一般代表一个单数的取值范围,不存在多位数的范围
  2. ```javascript
  3. reg=/^[1-9]$/
  4. reg.test("1") //=>true
  5. reg = /^[18]$/;
  6. reg.test("1") //=>true
  7. reg.test("8") //=>true
  8. reg.test("18") //=>false
  9. reg = /^[10-29]$/; //=> 1 或 0-2 或者 9
  10. reg.test("1") //=>true
  11. reg.test("9") //=>true
  12. reg.test("0") //=>true
  13. reg.test("2") //=>true
  14. reg.test("10") //=>false

四、常用的正则表达式

1、验证是否为有效数字

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

2、验证密码

  1. > 规则分析
  2. 1、数字、字母、下划线
  3. 2616
  4. let reg = /^[0-9a-zA-Z_]{6,16}$/
  5. let reg = /^\w{6,16}$/

3、验证真实姓名

  1. > 规则分析
  2. 1、汉字 /^[\u4E00-\u9FA5]$/
  3. 2、名字长度 2~10
  4. 3、可能有译名 "尼古拉斯·赵四"
  5. let reg = /^[\u4E00-\u9FA5]{2,10}(·[\u4E00-\u9FA5]{2,10}){0,2}$/

4、验证邮箱

  1. > 规则分析
  2. 1、开头数字字母下划线(1-多位) \w+
  3. 2、还可以 -数字字母下划线 或者 .数字字母下划线, 整体零到多次,邮箱的名字数字字母下划线、-、. 几部分组成,
  4. 但是-和. 不能连续出现,也不能作为开始 (-\w+ | \.\w+)*
  5. 3、对@后面名字补充
  6. 多域名 .com.cn @[A-Za-z0-9]+
  7. 企业邮箱 zxt.zhufeng-peixun.office.com ((\.|-))[A-Za-z0-9]+)*
  8. 4、匹配最后的域名(.com/.cn./.org/.edu/.net...) \.[A-Za-z0-9]+
  9. let reg = /^\w+((-\w)|(\.\w+))*@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/

5、身份证号码

  1. > 规则分析
  2. 1 18 最后一位可能是X
  3. let reg = /^\d{17}(\d|X)$/
  4. 2、身份证前六位代表 省市县 130625
  5. 3、中间八位年月日
  6. 4、最后四位
  7. X或者数字
  8. 倒数第二位 偶数是女 奇数是男
  9. 剩下几位是算法算出来的
  10. let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|X)$/;
  11. reg.exec("13062519941001203X")
  12. // ["13062519941001203X", "130625", "1994", "10", "01", "3", "X"...]
  13. // 可以通过身份证查询省市年月日性别生日等

6、 手机号验证

  1. // 11 位 18 | 17 | 13 | 15 开头
  2. let reg = /^1(8|7|3|5|4|6|9)\d{9}$/
  3. let reg = /^1[3456789]\d{9}$/

五、两种正则创建方式的区别

1、构造函数因为传递的是字符串, “\” 需要写两个才代表斜杠

  1. let reg = /\d+/g
  2. let reg = new RegExp("\\d+","g")

2、正则表达式中的部分内容是变量存储的值
两个斜杠抱起来的都是元字符,如果正则需要包含某个变量的值,不能使用字面量方式创建

  1. let type = "zhufeng"
  2. reg=/^@"+type+"@$/
  3. reg.test("@zhufeng@") //=> false
  4. reg.test('@"""typeeee"@') //=> true
  5. reg=new RegExp("^@"+type+"@$");
  6. reg.test("@zhufeng@") //=>true

六、正则的捕获

实现正则捕获的方法, 实现正则捕获的前提是实现正则匹配

1. 正则RegExp.prototype上面支持的正则表达式处理的方法

  • test
  • exec

    1、基于exec实现正则的捕获
    捕获的结果是null或者一个数组,
    数组第一项:是捕获到的内容,
    其余项:是对应小分组单独捕获的内容
    index:当前捕获内容在字符串中的起始索引
    input:原始字符串
    2、每执行一次exec,只能捕获到一个符合正则的规则,默认下,都是匹配到第一个,这是正则匹配的懒惰性
    出现懒惰性的原因是lastindex匹配捕获完成后值不会改变,都是0开始,解决的方式,
    匹配后改变lastindex的值,可以手动修改验证
    设置全局匹配修饰符g后,第一次匹配完后,lastIndex会自己修改

    2. 字符串String.prototype上支持正则表达式处理的方法

  • replace

  • match
  • splite
  1. let str = "zhufeng2019yangfang2020qihang2021"
  2. let reg = /\d+/
  3. reg.test(str)
  4. reg.lastIndex //=> 0
  5. reg.exec(str) //=> 2019
  6. reg.exec(str) //=> 2019
  7. // 解决正则捕获懒惰性的方法就是:全局修饰符g
  8. let reg = /\d+/g
  9. reg.lastIndex //=> 0
  10. reg.exec(str) //=> 2019
  11. reg.lastIndex //=> 11
  12. reg.exec(str) //=> 2020

3、编写一个方法execAll,执行一次可以把所有匹配的结果捕获到(前提是正则一定要设置全局修饰符g)

  1. ~function(){
  2. function execAll(str=""){
  3. //=> this:RegExp的实例(当前操作的正则)
  4. //=> 进来后的第一件事是看正则是否设置了g,不设置不能使用 dir(reg)
  5. if(!this.global) return this.exec(str)
  6. let ary = [],
  7. res = this.exec(str);
  8. while (res) {
  9. ary.push(res[0])
  10. res = this.exec(str);
  11. }
  12. return ary.length===0?null:ary
  13. }
  14. RegExp.prototype.execAll = execAll
  15. }()
  16. let str = "zhufeng2019yangfang2020qihang2021"
  17. let reg = /\d+/g
  18. reg.execAll(str) //=> ["2019", "2020", "2021"]

4、字符串的match方法

match不加g也不能全部匹配到,不加g也只能匹配到第一个数组

  1. let reg = /\d+/g
  2. let str = "zhufeng2019yangfang2020qihang2021"
  3. console.log(str.match(reg));

七、正则的分组捕获

1、分组捕获 match和exec

  1. // 身份证号码
  2. let str = "13062519941001203X"
  3. let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(?:\d|X)$/;
  4. reg.exec(str)
  5. str.match(reg)
  6. // ["13062519941001203X", "130625", "1994", "10", "01", "3", "X" ...]
  7. //=> 第一项:大正则匹配的结果
  8. //=> 其余项:每个小分组单独匹配捕获的结果
  9. //=> 如果设置了分组(改变优先级),但是捕获的时候不需要单独捕获,可以基于?:来处理,只匹配不捕获

match和exec都可以进行大正则的匹配,match的好处是在g的模式下,match可以全部匹配到大正则的,无法匹配到小正则的分组,需要通过exec通过循环得到

  1. let str = "{0}年{1}月{2}日"
  2. let reg = /\{(\d+)\}/;
  3. reg.exec(str)
  4. str.match(reg) //=> (2) ["{0}", "0", index: 0, input: "{0}年{1}月{2}日"...]
  5. // 不设置g只匹配一次,exec和match获取的结果一致,既有大正则匹配的信息,也有小分组匹配的信息
  6. let reg = /\{(\d+)\}/g;
  7. str.match(reg) //=> ["{0}", "{1}", "{2}"]
  8. reg.exec(str) //=> ["{0}", "0", index: 0, input: "{0}年{1}月{2}日",...]

2、”分组引用”

分组引用就是通过”\数字”让其代表和对应分组出现一摸一样的内容

  1. let str = "book"; //=> "good"、"look"、"moon"、"foot"
  2. let reg = /^[a-zA-Z](o\1o)[a-zA-Z]$/
  3. reg.test("book") //=> true
  4. reg.test("moon") //=> true

八、正则捕获的贪婪性

正则捕获的时候是按照当前正则所匹配的最长结果来获取的

  1. let str = "写话2019@2020庞大的";
  2. let reg = /\d+/g;
  3. str.match(reg) //=> ["2019","2020"]

取消正则的贪婪性,在量词元字符后面设置 “ ? “ , 按照正则匹配最短结果来获取

  1. let str = "写话2019@2020庞大的";
  2. let reg = /\d+?/g;
  3. str.match(reg) //=> ["2", "0", "1", "9", "2", "0", "2", "0"]

九、正则中的问号的五大作用

  • 问号左边是非量词元字符,本身代表量词元字符,出现零到一次
  • 问号左边是量词元字符,取消捕获时候的贪婪性
  • (?:) 只匹配不捕获
  • (?=) 正向预查
  • (?!) 负向预查

十、其他正则捕获的方法

1、test也能捕获, RegExp.$1 (本意是匹配)

RegExp.$1~RegExp.$9 获取当前本次正则匹配后,第一个到第九个分组的信息,这个在真实项目中不实用

  1. let str = "{0}年{1}月{2}日"
  2. let reg = /\{(\d+)\}/g
  3. reg.test(str) //=> true
  4. RegExp.$1 //=> 0
  5. reg.test(str) //=> true
  6. RegExp.$1 //=> 1
  7. reg.test(str) //=> true
  8. RegExp.$1 //=> 2
  9. reg.test(str) //=> false
  10. RegExp.$1 //=> 2 存储的是上一次捕获的结果

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

3、案例一:把”rock@2019|rock@2020” 中的rock替换成石头

1、把”rock@2019|rock@2020” 中的rock替换成石头

  1. let str = "rock@2019|rock@2020";
  2. // 不用正则执行一次只能替换一次
  3. str.replace("rock","石头").replace("rock","石头")
  4. str.replaceAll("rock","石头")
  5. // 使用正则
  6. str.replace(/rock/g,"石头")

2、把”rock@2019|rock@2020” 中的rock替换成rockHello
当实现这个需求的时候会发现replace无法实现,因为每次都从最开始找(类似于正则捕获的懒惰性),没有 办法找到最后面匹配
解决方法就是用replaceAll或者正则匹配

  1. let str = "rock@2019|rock@2020";
  2. str.replace("rock","rockHello").replace("rock","rockHello") //=> "rockHelloHello@2019|rock@2020"
  3. str.replaceAll("rock","rockHello") //=> "rockHello@2019|rockHello@2020"
  4. //=> 基于正则g可以实现
  5. str.replace(/rock/g,"rockHello") //=> "rockHello@2019|rockHello@2020"

4、案例二: 把时间字符串进行处理

把2019-08-13变成2019年08月13日

  1. let time = '2019-08-13'
  2. let reg = /^(\d{4})-(\d{1,2})-(\d{1,2})$/g
  3. time = time.replace(reg, "$1年$2月$3日") //=> "2019年08月13日"
  4. //=> 可以这样处理 [str].replace([reg],[function])
  5. // 1、首先进行捕获,匹配到几次就会传递给函数执行几次
  6. // 2、不仅方法执行,而且还根据exec捕获的内容一致的信息,传递给该方法,大正则匹配的内容,小正则匹配的信息
  7. time=time.replace(reg,(big,...arg)=>{
  8. let [$1,$2,$3] = arg;
  9. return $1+'年'+$2+'月'+$3+'日'
  10. })

5、案例三:所有单词首字母大写

  1. let str = "good good study, day day up"
  2. let reg = /\b([a-zA-Z])[a-zA-Z]*\b/g;
  3. // 函数被执行6次,每次都把匹配的信息传递给函数
  4. // 每一次arg:["good","g"]
  5. str = str.replace(reg,function(...arg){
  6. let [content,$1] = arg
  7. $1=$1.toUpperCase();
  8. content = content.substring(1)
  9. return $1+content;
  10. })

6、案例四:验证一个字符串中,哪个字母出现的次数最多,多少次

  1. let str = "hahatiantianxiangshanghhh"
  2. /*==方法一:去重思维==*/
  3. // 根据对象的key唯一原理去求出
  4. let obj = {};
  5. ([]).forEach.call(str,(char)=>{
  6. if(typeof obj[char] !=='undefined'){
  7. obj[char]++
  8. return
  9. }
  10. obj[char]=1
  11. })
  12. console.log(obj)
  13. let max = 1,res=[];
  14. for(let key in obj){
  15. let item = obj[key]
  16. item > max ? max = item : null;
  17. }
  18. for(let key in obj){
  19. let item = obj[key]
  20. if(item===max){
  21. res.push(key)
  22. }
  23. }
  24. console.log(`出现次数最多的字符是${res},出现了${max}次`) ) //=> 出现次数最多的字符是a,h,出现了6次
  25. /*==方法二:对字母进行排序==*/
  26. let str = "hahatiantianxiangshanghhh"
  27. str = [...str].sort((a,b)=> a.localeCompare(b)).join(''); // "aaaaaagghhhhhhiiinnnnsttx"
  28. let ary = str.match(/([a-zA-Z])\1+/g).sort((a,b)=>b.length-a.length);
  29. let max = ary[0].length,res=[ary[0].substr(0,1)];
  30. for(let i=1;i<ary.length;i++){
  31. if(ary[i].length<max){
  32. break;
  33. }
  34. res.push(ary[i].substr(0,1))
  35. }
  36. console.log(`出现次数最多的字符是${res},出现了${max}次`) //=> 出现次数最多的字符是a,h,出现了6次
  37. /*==方法三:按照字母排序,然后通过倒叙的当时进行带变量的正则匹配==*/
  38. let str = "hahatiantianxiangshanghhh"
  39. str= str.split('').sort((a,b)=>a.localeCompare(b)).join(''); //=> "aaaaaagghhhhhhiiinnnnsttx"
  40. let res=[],max=0,flag=false;
  41. for(let i=str.length;i>0;i--){
  42. let reg = new RegExp("([a-zA-Z])\\1{"+(i-1)+"}","g")
  43. str.replace(reg,(big,$1)=>{
  44. max=i;
  45. res.push($1)
  46. flag = true
  47. })
  48. if(flag)break;
  49. }
  50. console.log(`出现次数最多的字符是${res},出现了${max}次`) //=>出现次数最多的字符是a,h,出现了6次
  51. /*==方法四:假设法==*/
  52. let str = "hahatiantianxiangshanghhh",
  53. max=0,
  54. letter,
  55. reg="",
  56. oldLength,
  57. letterCount,
  58. result=[];
  59. while(str!==""){
  60. oldLength = str.length;
  61. letter = str.substr(0,1)
  62. reg = new RegExp(letter,"g")
  63. str = str.replace(reg,"");
  64. letterCount = oldLength - str.length;
  65. if(letterCount>=max){
  66. max = letterCount
  67. result.push(letter)
  68. }
  69. }
  70. console.log(`出现次数最多的字符是${result},出现了${max}次`) //=>出现次数最多的字符是a,h,出现了6次

7、案例五:时间字符串格式化

time = “2019-8-13 16:51:3”; time = “2019/8/13 16:51:3”;
转化成08月13日 16时51分、2019年08月13日

  1. /*
  2. * 时间字符串格式化方法
  3. * formaTime:时间格式化处理
  4. * @params
  5. * templete:[string] 我们最后期望获取日期格式的模版,模版规则{0}->年 {1-5}->月日时分秒
  6. * @return
  7. * [string]格式化后的时间字符串
  8. * by rockshang on 2020/11/17
  9. */
  10. !function(){
  11. function formateTime(template="{0}年{1}月{2}日 {3}时{4}分{5}秒"){
  12. let timeAry = this.match(/\d+/g);
  13. return template.replace(/\{(\d+)\}/g,(...[,$1])=>{
  14. let time = timeAry[$1] || "00";
  15. return time.length < 2 ? "0" + time : time;
  16. });
  17. }
  18. // 扩展到内置类String.prototype上
  19. ["formateTime"].forEach(item=>{
  20. String.prototype[item] = eval(item);
  21. });
  22. }();
  23. // 使用
  24. let str = "2019/8/13 16:51:3";
  25. str = "2019/8/13 16:51:3";
  26. str.formateTime() //=>2019年08月13日 16时51分03秒
  27. str.formateTime("{0}年{1}月{2}日")

8、案例六:获取路径上的参数queryURLParams

  1. /*
  2. * queryURLParams:获取URL地址问号和后面的参数信息(可能也包含HASH值)
  3. * @params
  4. * @return
  5. * [object]把所有问号参数信息以键值对的方式存储起来并且返回
  6. * by rockshang on 2020/11/17
  7. */
  8. !function(){
  9. function queryURLParams(){
  10. let obj = {};
  11. this.replace(/([^#?&#]+)=([^#?&#]+)/g,(...[,$1,$2])=>obj[$1] = $2)
  12. this.replace(/#([^#?&#]+)/g,(...[,$1])=>obj['HASH']=$1);
  13. return obj
  14. }
  15. ["queryURLParams"].forEach(item=>{
  16. String.prototype[item] = eval(item);
  17. });
  18. }();
  19. // 使用
  20. let url = "http://www.baidu.com?lx=1&from=wx#video"; //=> {lx:1,from:'wx',HASH:'video'}
  21. url.queryURLParams()

9、案例七:千分符

let num = 15628954; ==> 15,628,954
普通实现方式, 字符串反转,循环

  1. let num = "13455533333";
  2. num = num.split('').reverse().join('');
  3. for(let i=2;i<num.length -1;i+=4){
  4. let prev = num.substring(0,i+1),
  5. next = num.substring(i+1);
  6. num = prev + "," + next;
  7. }
  8. num = num.split('').reverse().join('');
  9. console.log(num)

通过正则实现:一行代码切分千位符
image.png

  1. /*
  2. * millimeter:实现大数字的千分符处理,使用正向预查?= 只匹配不捕获
  3. * @params
  4. * @return
  5. * [string]千分符后的字符串
  6. * by rockshang on 2020/11/17
  7. */
  8. !function(){
  9. function millimeter(){
  10. return this.replace(/\d{1,3}(?=(\d{3})+$)/g,content=>content+',')
  11. }
  12. ["millimeter"].forEach(item=>{
  13. String.prototype[item] = eval(item);
  14. });
  15. }();
  16. let num = "15628954";
  17. num.millimeter()