JavaScript 正则,看这篇就够了

正则的使用,相信大家工作中常用到的莫过于,表单验证:验证用户输入的内容是否符合我们设定的规则,例如:邮箱、电话、密码...,当然公司业务不同,手写能力强同学也可以用数据采集:在一堆数据中把我们需要的数据获取到

JavaScript 正则,看这篇就够了

作者:duffy
原文:https://juejin.im/post/5acb4d3f6fb9a028c813295e

写在前面

正则的使用,相信大家工作中常用到的莫过于,表单验证:验证用户输入的内容是否符合我们设定的规则,例如:邮箱、电话、密码...,当然公司业务不同,手写能力强同学也可以用数据采集:在一堆数据中把我们需要的数据获取到,今天顺带总结一下,例如::

  • URL传参(问号传参) 从列表页到详细页(或者其他的页面),我们点击不同的列表,在详细页面看到的内容也不同,但是详细页是同一个页面,想要展示不同的内容,需要把URL地址中的,?后面传递进来的参数获取到,通过传递过来的参数值的不一样展示不同的内容
  • 信息采集:把一些数据转变为我们想要的格式,例如:"2018-4-3 12:14:00" ->"2016年04月03日"

今天顺带总结一下;

正则是什么

正则就是用来处理字符串的:匹配字符串的格式是否符合既定的格式(正则的匹配)、把一个字符串中符合既定格式的内容获取到(正则的捕获);test 和 exec 是正则里面的方法
test->匹配

   var reg = /\d/;//-> \d一个0-9之间的数字 ->包含一个0-9之间的数字的规则
       console.log(reg.test("2016"));//->true
       console.log(reg.test("duff990fff"));//->true
       console.log(reg.test("dufffff"));//->false

exec->捕获

   var reg = /\d/g;
       console.log(reg.exec("2016"));//->["2", index: 0, input: "2016"]
       console.log(reg.exec("2016"));//->["0"...]
       console.log(reg.exec("2016"));//->["1"...]
       console.log(reg.exec("2016"));//->["6"...]

正则的组成

每个正则都是由元字符和修饰符两部分组成, “/”里面的 内容称为元字符 “/”[修饰符] g、i、m

    g(global)->全局匹配
    i(ignoreCase)->忽略大小写匹配
    m(multiline)->换行匹配

正则常用的元字符

以下列出的都是常用的元字符,如果是不了解的童鞋需要,浪费你10分钟时间记一下,终身受益哈~
元字符:只要在//之间包含的所有的字符都是元字符

  1. 具有特殊意义的元字符
      \d -> 匹配一个0-9的数字,相当于[0-9],和它相反的是\D ->匹配一个除了0-9的任意字符
       \w -> 匹配一个0-9、a-z、A-Z、_的数字或字符,相当于[0-9a-zA-Z_]
       \s -> 匹配一个空白字符(空格、制表符...)
       \b -> 匹配一个单词的边界
       \t -> 匹配一个制表符
       \n -> 匹配一个换行
       . -> 匹配一个除了\n以外的任意字符
       ^ -> 以某一个元字符开头
       $ -> 以某一个元字符结尾
       \ -> 转义字符
       x|y -> x或者y的一个
       [xyz] -> x、y、z中的任意一个
       [^xyz] -> 除了xyz中的任意一个字符
       [a-z] -> 匹配a-z中的任意一个字符
       [^a-z] -> 匹配除了a-z中的任意一个字符
       () -> 正则中的分组
  1. 代表出现次数的"量词元字符"
    ->+ : 出现一到多次
    ->* : 出现零到多次
    ->? : 出现零到一次
    ->{n} : 出现n次
    ->{n,} : 出现n到多次
    ->{n,m} : 出现n-m次

小试牛刀

-> ^/$
       var reg = /\d+/;//->包含一到多个数字(0-9)即可
       console.log(reg.test("duffy2016"));//->true

       reg = /^\d+$/;//->只能是一到多个数字
       console.log(reg.test("duffy2016"));//->false
       console.log(reg.test("2016"));//->true


-> .
       var reg = /^2.6$/;
       console.log(reg.test("2.6"));//->true
       console.log(reg.test("2@6"));//->true
    
       reg = /^2\.6$/;//->\是转义字符:把.这个特殊意义(任意字符)的元字符转变为只代表本身意义(小数点)的一个普通元字符
       console.log(reg.test("2.6"));//->true
       console.log(reg.test("2@6"));//->false


//->[]
    //->在中括号中出现的所有字符(不管之前代表什么意思),在这里都是只代表本身的意思
    //var reg = /^[2.3]$/;//->.这里只代表小数点,不是任意字符了
    //reg = /^[\dz]$/;//->\d本身整体就是0-9之间的数字,在这里还是这个意思
    
//->在中括号中出现的两位数不是一个两位数,而是左边或者右边的
//var reg = /^[10-23]$/;//->1或者0-2或者3

在中括号中"-"具有连字符的作用,如果只想表示-,需要把其放在末尾
    //var reg = /^[12-]$/;

    //->中括号本身也有特殊的意思,如果需要只代表中括号本身的意思,需要进行转义
    //var reg = /^\[\d+\]$/;//->"[200]"

//->x|y
//var reg = /^1|2$/;//->和这个有区别:/^[12]$/
//->1、2、12

    //var reg = /^10|28$/;
    //->10、28、1028、102、108、128、028 ->不是我们想要的那个10或者28了

()分组:把一个大正则划分成几个小正则

  1. 改变正则的默认优先级
//var reg = /^(10|28)$/;//->10或者28
  1. 分组的第二个作用:分组引用
    "wood" "foot" "week" "feel" "door" "food" "good" "cool"...
    var reg = /^[a-z]([a-z])\1[a-z]$/i;//->\1出现和第一个分组一模一样的内容

5、创建一个正则也有两种方式:字面量方式、实例创建的方式

    //->实例创建第一参数值是字符串
    //->想要和字面量方式保持统一的话,对于\d \w \n...这些都需要多加一个\,使其\d具有自己的特殊的意义
  var reg = /^\d+$/ig;
console.log(reg.test("2016"));//->true
reg = new RegExp("^\d+$", "ig");
console.log(reg.test("2016"));//->false
reg = new RegExp("^\\d+$", "ig");
console.log(reg.test("2016"));//->true
//->对于[]、()这类的是没有区别的
    //    var reg = /^[0-9]$/;
    //    console.log(reg.test("0"));//->true
    //    reg = new RegExp("^[0-9]$");
    //    console.log(reg.test("0"));//->true

    //->在实例创建的方式中,我们只要出现\,基本上都是要写\\的
    //    var reg = /^\[100\]$/;
    //    console.log(reg.test("[100]"));//->true
    //    reg = new RegExp("^\[100\]$");
    //    console.log(reg.test("[100]"));//->false
    //    reg = new RegExp("^\\[100\\]$");
    //    console.log(reg.test("[100]"));//->true

当一个正则表达式中需要把一个变量的值作为一个动态的规则:我们只能使用实例创建的方式
// var reg = /^duffy"+num+"peixun$/;//->//之间包起来的都是元字符,有的是特殊的,有的就是代表本身意思的 ->以z开头,hufeng,出现一到多个",nu,出现一个到一个m,",peixun ->总之一句话:字面量方式中不存在什么字符串,也就没有所谓的字符串拼接,把变量的值拼接过来这一说了

    //    console.log(reg.test("duffy2016peixun"));//->false

    //    var num = 2016;
    //    var reg = new RegExp("^duffy" + num + "peixun$");
    //    console.log(reg.test("duffy2016peixun"));//->true
//字面量创建出来的是基本数据类型的值(不是严谨的实例,因为不能使用instanceof 检测是否是Number的实例),实例创建出来的是对象数据类型的值
//    var num = 12;
//    var num = new Number(12);

常用的正则表达式

1、手机号:11位数字、都是以1开头的

var reg = /^1\d{10}$/;

2、真实姓名(中国):两到四位的汉字

var reg = /^[\u4e00-\u9fa5]{2,4}$/;

3、验证邮箱

// [email protected]
// [email protected]
// [email protected]
// [email protected]
//var reg7 = /^[\w.-]+@$/  //@分解两边 左边任意,\w 数字,大小写字符_ - . 右边
var reg7 = /^[\w.-]+@([1-9]|[a-z]|[A-Z])+(\.[A-Za-z]{2,4}){1,2}$/
console.log(reg7.test('[email protected]'))

4、验证有效数字的

  0 -12 -12.3 -12.0 12.3 12.0
    ->可能出现"-"也可能不出现,出现的话只能出现一次
    ->整数部分是一到多个数字,但是两位数及以上的话不能以0开头
    ->小数部分可能有可能没有,一但有必须是 .后面跟一位或者多位数字
    var reg = /^-?(\d|([1-9]\d+))(\.\d+)?$/;

5、年龄:18-65之间

    18-19  /^1(8|9)$/
    20-59  /^[2-5]\d$/
    60-65  /^6[0-5]$/
    var reg = /^((18|19)|([2-5]\d)|(6[0-5]))$/;

正则的捕获

正则的捕获分为两个阶段:
匹配:首先验证字符串和正则是否匹配,不匹配的话捕获到的结果为null

       var str = "dafei";
       var reg = /\d+/;
       console.log(reg.exec(str));//->null

捕获:把正则匹配到的内容捕获到:捕获到的结果是一个数组,数组第一项是当前正则匹配捕获的内容,index:捕获的开始索引位置,input:捕获的原始字符串
每一次执行exec只能捕获到一个匹配的,想把所有匹配的都捕获到,至少要执行多次 ->但是一般情况下,我们不管执行多少次,每一次捕获的内容都是和第一次一模一样,后面的2017是捕获不到的"正则捕获的懒惰性"

       var str = "duffy2016peixun2017";
       var reg = /\d+/;
       console.log(reg.exec(str));//->["2016", index: 7, input: "duffy2016peixun2017"]
       console.log(reg.exec(str));//->["2016"...]

为啥会出现懒惰性? reg.lastIndex:正则每一次捕获的时候,在字符串中开始查找的索引, 正则每一次捕获结束后,默认的没有把lastIndex值进行修改,lastIndex一直是零,导致第二次捕获还是从字符串的起始位置开始查找,导致每一次捕获的都是第一个和正则匹配的

    var str = "duffy2016peixun2017";
       var reg = /\d+/;
       console.log(reg.lastIndex);//->0 捕获的时候是从字符串开始的位置进行查找的
       console.log(reg.exec(str));//->["2016"...]
       console.log(reg.lastIndex);//->0 第二次捕获的话还是从字符串索引为零的位置开始查找
       console.log(reg.exec(str));//->["2016"...]

有n个的匹配的就需要执行n次exec这个方法,比较的麻烦,生活如此美好,何必这么麻烦? ->字符串中提供了一个叫做match的方法,这个方法可以一次执行把所有匹配的捕获到

       var str = "duffy2016peixun2017";
       var reg = /\d+/g;//->不管用哪个方法,g是不能少的
       console.log(str.match(reg));//->["2016", "2017"]

但是match也有自己的局限性? 如果正则中出现分组,而且需要执行多次exec才能全部捕获的,使用match不能把分组的内容捕获到.最佳解决方案 ---replace,replace 天生为正则而生

实战

格式化时间字符串

var str = "2016-04-03";//->"2016年04月03日"
let reg = /^([1-9]\d{3})-(0?[1-9]|1[1-2])-([02]\d|[3][0-1])$/
console.log(reg.test(str))
str.replace(reg,function(){
  console.log(arguments)
  return arguments[1] + '年' + arguments[2] + '月' + + arguments[3] + '日'
})

function format(str) {
  var reg = /^([1-9]\d{3})-(0?[1-9]|1[1-2])-([02]\d|[3][0-1])$/g
  if (!reg.test(str)) return '输入日期格式不合法'
  return (str.replace(reg,function(){
    // console.log(arguments)
    return arguments[1] + '年' + arguments[2] + '月' + + arguments[3] + '日'
  }))
}
console.log(format('2018-12-30'))   -> '2018年12月30日

数字大写转换

   var str = "123678";//->"壹贰叁陆柒捌"
       var ary = ["零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"];
       str = str.replace(/\d/g, function () {
           //console.log(arguments[0]);//->每一次捕获到的内容(我们要的数字)
           return ary[arguments[0]];
       });
       console.log(str);

简易模板引擎实现的原理

var data = ['duffy', '27', 'china', 'javascript']
var str = "my name is {0},my age is {1},i com from {2},i can do {3}~~";

var reg = /\{(\d)\}/g
console.log(reg.test(str))
str = str.replace(reg, function(){
  return data[arguments[1]]
})

另:一些正则的网站

https://regexr.com/
https://regex101.com/
https://tool.lu/regex/