# 14、正则

TIP

正则就是一个规则,用来处理字符串的规则 1、正则匹配 编写一个规则,验证某个字符串是否符合这个规则,正则匹配使用的是test方法 2、正则捕获 编写一个规则,在一个字符串中把符合规则的内容都获取到;正则的exec、字符串的split、replace、match等方法都支持正则

# 正则的创建方式

TIP

1.字面量方式:var reg=/\d/; 2.实例创建方式 var reg=new RegExp('a','b');支持两个参数,第一个为元字符,第二个是修饰符

# 正则两种创建方式的区别

区别

1,字面量方式中出现一切字符都会按找元字符来解析,所以不能进行变量值的拼接,而实例创建的方式是可以的。 2,字面量方式中直接写\d就可以,而在实例创建方式中需要把它转义;只需要在\d之前再加一个\即可。即\\d

# 正则的元字符和修饰符

任何一个正则都是由元字符修饰符组成的

# 修饰符

ignoreCase(i):忽略大小写;

global(g):全局匹配;找到所有的匹配而不是只找到第一个匹配

multiline(m):多行匹配;

# 元字符

代表出现次数的量词元字符

    *       :	  出现零到多次
    ?       :	  出现0次或1+       :	  出现1到多次
    {n}     :	  出现n次
    {n,}    :	  出现n到多次
    {n,m}   :	  出现n到m次

具有特殊意义的元字符

    \       :   转义字符(两个\\就表示两个\\)
    ^       :   以某一个元字符开始,不占位置。(只对字符串的开头起作用)
    $       :   以某一个元字符结尾,不占位置。(只对字符串的末尾起作用)
    .       :   除了\n(换行符)以外的任意字符
    ()      :   分组
    x|y     :   x或y中的一个
    [xyz]   :   x或者y或者z中的一个
    [^xyz]  :   除了x y z的任何一个字符
    [a-z]   :   a-z之间的任何一个字符
    [^a-z]  :   除了a-z的任何一个字符
    \d      :   一个0-9之间的数字
    \D      :   匹配一个非数字字符
    \b      :   一个边界符:“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”,单词左右两边是边界,-的左右两边也是边界,所以会把zhu-mu-la-ma-feng当作5个单词,而我们想把它当成一个单词
    \n      :   匹配一个换行符。
    \s      :   匹配一个空白字符 (空格,制表符,换页符)
    \w      :   数字,字母,下划线中的任意一个字符
    ?:      :   只匹配不捕获
    ?=      :   先行断言      x(?=y)   当且仅当'x'后面跟着'y'时,匹配'x'?!      :   正向否定查找   x(?!y)   当且仅当'x'后面不是'y'时,匹配'x'
    ?<=     :   后行断言      (?<=y)x   当且仅当'x'前面跟着'y'时,匹配'x'
    ?<!     :   反向否定查找  (?<!y)x   当且仅当'x'前面不是'y'时,匹配'x'

# 正则中[]的特殊含义

中括号

  • 1、[] 里面出现的元字符一般都是代表本身意义的,比如[xyz](表示xyz其中的一个),但是有一些依然表示符号本身的意思如[\d\w](表示数字或字母其中的一个)

  • 2、中括号中的横线(-)代表两种意思

    • 出现在中间:[a-z0-9]-代表范围连接符
    • 出现在末尾:[a-z_0-9-]:如果-的左右两边任何一边没有内容那么-代表本身的意思,即匹配一个横线。
  • 3、[]中出现的两位数不代表本身意思,而是以一位数来解析,如[18]代表 1或8 两个数字中的一个;

中括号中需要转义的字符

  • 1、反斜杠(\)必须转义
  • 2、方括号([,])必须转义
  • 3、^ 在 首位 必须转义,- 在 中间 必须转义

# 正则中()的作用

提高优先级

正则中的分组,也可以理解为一个大正则中的一个正则(包起来的部分是个一个整体);我们可以使用小括号改变一些默认的优先级。如(x|y)

分组引用

(\d)\1\1代表引用和前面相同的数字;

分组捕获

正则捕获的时候不仅可以把大正则匹配的内容捕获到,而且也会把小括号中的内容捕获到

# 常用的正则表达式编写

TIP

注意:正则中如果同时加入了^$表示只能是xxx,不加的话表示包含xxx,而且^只对字符串的开头起作用,$只对字符串的结尾起作用;

reg = /^-?((\d)|([1-9]\d+))(\.\d+)?$/  有效数字正则

reg = /^1\d{10}$/ 手机号验证

//=>/^[\u4E00-\u9FA5]$/ 中文汉字的正则
// 姓名的正则:
reg = /^[\u4E00-\u9FA5]{2,10}$/;
reg = /^[\u4E00-\u9FA5]{2,10}(·[\u4E00-\u9FA5]{2,10})?$/;

// 邮箱正则
/*
  * 以数字字母下划线开头
  * @前面可以是 数字、字母、下划线、-、. 这些符号
  * 不能把 -和. 连续出现,出现一次后面必须跟数字字母下划线
  *  
  * @后面的部分支持
  *   企业邮箱
  *   .com.cn 多域名情况
  */
reg=/^\w+((-\w+)|(\.\w+))*@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/

身份证:
前六位:省市县
接下来八位 出生年++日
接下来的两位:没什么用
倒数第二位数字 奇数为男,偶数为女
reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|X)$/;

/* web URL 解析正则 */
const reg = /^(https?:\/\/)([a-zA-Z0-9\._-]+\.[a-zA-Z]{2,6})(:\d+)?([a-zA-Z0-9\/_-]+\/[a-zA-Z0-9_-]*)?(\?(=?&?[a-zA-Z0-9_-]*)*)?$/i

# web URL解析

使用正则

缺点:不会对URL进行解码

const str = 'https://carbon.now.sh/my/collection/?bg=rgba(100,180,250,1)&wc=true&wa=true&code=const%20vpc%20%3D%20Vpc.fromLookup(this%2C%20%27MyExistingVPC%27%2C%20%7B%20isDefault%3A%20true%20%7D)%3B%0Anew%20cdk.CfnOutput(this%2C%20%22MyVpc%22%2C%20%7Bvalue%3A%20vpc.vpcId%20%7D)%3B#qq#'
const urlParse = function (url) {
  const reg = /^(?:(https?:)\/\/)?([a-zA-Z0-9\._-]+\.[a-zA-Z]{2,6})(?::(\d+))?([a-zA-Z0-9\/_-]*)?\/?(([?&][^?#=&]+=[^?#=&]*)*)?(#.*)?$/i
  /*  
    协议($1):(https?:)
    域名($2):([a-zA-Z0-9\._-]+\.[a-zA-Z]{2,6})
    端口号($3):(:\d+)
    路径($4):([a-zA-Z0-9\/_-]*)
    参数($5):(([?&][^?#=&]+=[^?#=&]*)*)
    哈希($7):(#.*)
    */
  reg.test(str)
  const { $1, $2, $3, $4, $5, $7 } = RegExp
  const res = {
    protocol: $1 || 'https:',
    hostname: $2,
    port: $3,
    origin: `${$1}//${$2}:${$3}`,
    host: `${$2}:${$3}`,
    pathname: $4,
    search: $5,
    hash: $7,
    query: {}
  }
  if ($5) {
    $5.replace(/[?&]([^?=&]+)(?:=([^?=&]*))?/g, function (k, group1, group2) {
      res.query[group1] = group2
    })
  }
  return res
}
const res = urlParse(str)
/*
console.log(res)
{
  "protocol": "https:",
  "hostname": "carbon.now.sh",
  "port": "",
  "origin": "https://carbon.now.sh:",
  "host": "carbon.now.sh:",
  "pathname": "/my/collection/",
  "search": "?bg=rgba(100,180,250,1)&wc=true&wa=true&code=const%20vpc%20%3D%20Vpc.fromLookup(this%2C%20%27MyExistingVPC%27%2C%20%7B%20isDefault%3A%20true%20%7D)%3B%0Anew%20cdk.CfnOutput(this%2C%20%22MyVpc%22%2C%20%7Bvalue%3A%20vpc.vpcId%20%7D)%3B",
  "hash": "#qq#",
  "query": {
    "bg": "rgba(100,180,250,1)",
    "wc": "true",
    "wa": "true",
    "code": "const%20vpc%20%3D%20Vpc.fromLookup(this%2C%20%27MyExistingVPC%27%2C%20%7B%20isDefault%3A%20true%20%7D)%3B%0Anew%20cdk.CfnOutput(this%2C%20%22MyVpc%22%2C%20%7Bvalue%3A%20vpc.vpcId%20%7D)%3B"
  }
}
*/

使用原生URL对象

const urlParse = function (url) {
  const url = new URL(str)
  if (url.search) {
    url.query = {}
    url.searchParams.forEach((value, key) => url.query[key] = value)
  }
  return res
}
const res = urlParse(str)
/*
console.log(res)
{
  href: 'https://carbon.now.sh/my/collection/?bg=rgba(100,180,250,1)&wc=true&wa=true&code=const%20vpc%20%3D%20Vpc.fromLookup(this%2C%20%27MyExistingVPC%27%2C%20%7B%20isDefault%3A%20true%20%7D)%3B%0Anew%20cdk.CfnOutput(this%2C%20%22MyVpc%22%2C%20%7Bvalue%3A%20vpc.vpcId%20%7D)%3B#qq#',
  origin: 'https://carbon.now.sh',
  protocol: 'https:',
  username: '',
  password: '',
  host: 'carbon.now.sh',
  hostname: 'carbon.now.sh',
  port: '',
  query:{
    bg: 'rgba(100,180,250,1)',
    code: 'const vpc = Vpc.fromLookup(this, 'MyExistingVPC', { isDefault: true });\nnew cdk.CfnOutput(this, \"MyVpc\", {value: vpc.vpcId });',
    wa: 'true',
    wc: 'true'
  }
  pathname: '/my/collection/',
  search: '?bg=rgba(100,180,250,1)&wc=true&wa=true&code=const%20vpc%20%3D%20Vpc.fromLookup(this%2C%20%27MyExistingVPC%27%2C%20%7B%20isDefault%3A%20true%20%7D)%3B%0Anew%20cdk.CfnOutput(this%2C%20%22MyVpc%22%2C%20%7Bvalue%3A%20vpc.vpcId%20%7D)%3B',
  searchParams: URLSearchParams {},
  hash: '#qq#'
}
*/

# 正则的捕获

# exec

正则的捕获:reg.exec(str)

捕获的内容是一个数组,数组中的第一项是当前正则捕获的内容,如果有小括号(),则第二项捕获的是小括号里的内容。

  • index:捕获内容在字符串中开始的索引位置。
  • input:捕获的原始字符串。如果没有匹配的返回null;

[懒惰性] 捕获的特点:执行一次捕获一次,每次执行exec只捕获第一个匹配的内容。 为什么会存在懒惰性?

  • 每个正则都自带lastIndex属性:正则每一次捕获在字符串中开始查找的位置,默认值为0;
  • 我们手动修改lastIndex的值也不会影响exec每次开始查找的索引。

如何解决只捕获第一个匹配(懒惰性)? 在正则末尾加一个修饰符g

原理: 修改lastIndex的值为上一次捕获内容之后的第一个字符的索引;加了g之后能保证下次捕获第二个匹配的内容; 通过加g 每次捕获结束后,lastIndex的值都会改变成最新的值,下次捕获从最新的位置开始找。如果没有找到符合的,则返回null; 如何解决exec执行一次捕获一次?

// 我的方法
RegExp.prototype.myExec=function(){
  var str=arguments[0]||'',ary=this.exec(str),ary1=[];
  if(this.global!==true)return ary;
  while(ary){
    ary1=ary1.concat(ary);
    ary=this.exec(str);
  }
  return ary1;
}
// 别人的方法
RegExp.prototype.myExec=function () {
  var str=arguments[0]||'', ary=this.exec(str),result=[];
if(!this.global)return ary;
  while (ary){
      result.push(ary);
      ary=this.exec(str);
  }
  return result;
}

# match

用字符串方法实现多次捕获

str.match(reg) 1、如果正则中加了修饰符g,那么执行一次就可以把所有匹配的结果捕获到 2、如果不加g,那么只捕获第一次匹配的结果。返回一个数组,数组中是捕获的每一项; [局限性] 在分组捕获中,如果正则不加修饰符g

  • 那么matchexec一样,可以捕获到小分组的内容。

如果正则加了修饰符g

  • match只能捕获到大正则匹配的内容,而对于小分组匹配的内容是无法捕获到的;

注意:由于testexec的原理相同,如果加了g,都是修改lastIndex值来匹配或者捕获,所以使用testexec都会修改lastIndex的值,所以使用exec捕获之前最好不要使用test

test也可以捕获符合的字符串

reg.test(str);
RegExp.$1// 只能捕获第1个小分组里面的内容
RegExp.$2// 只能捕获第2个小分组里面的内容
RegExp.$3// 只能捕获第2个小分组里面的内容
同reg.exec(str);// 也能使用RegExp.$1来捕获第1个小分组
RegExp.$1// 只能捕获第1个小分组里面的内容
RegExp.$2// 只能捕获第2个小分组里面的内容

# 所有的支持正则的方法都可以实现正则的捕获(一般都是字符串的方法)

字符串中常用的支持正则的方法:

  • match
  • replace
  • split:如果给的正则中包含小分组,那么会把小分组中的内容也捕获到;

?:只匹配不捕获, 只能放在小分组的开头。如果加在分组中,那么只会匹配该分组中的内容,而不会捕获 /(?:&|=)/

计算是第几个分组的时候,我们从左向右找(即可

var reg = /^-?(\d|([1-9]\d+))(\.\d+)?$/;//=>计算是第几个分组的时候,我们从左向右找 ( 即可

# replace(第二个参数支持直接在字符串中使用$1-9)

替换字符串

在不使用正则的情况下,每次执行只能替换第一个匹配的字符串,而且每次执行都是从字符串索引为0的位置开始查找 第一个参数为正则时,正则捕获几次,就替换几次,第二个参数是函数时,正则捕获几次,函数就执行几次,函数中返回的是什么,就相当于把正则捕获的内容替换成什么。 第二个参数为函数时,正则捕获几次,函数就执行几次,函数执行的时候还会默认的传入三个参数: context(捕获的内容) index(捕获内容在字符串中的开始索引) input(原始的字符串)

str.replace(/珠峰/g,function(context,index,input){
  // arguments[0]大正则匹配的内容
  //如果正则中有分组的话
  // arguments[1]第1个小分组匹配的内容
  // arguments[2]第2个小分组匹配的内容
  // …………直到小分组的内容显示完
  // argument[n]每一次捕获的开始索引
  // argument[n+1]原始字符串
  return // 的是什么就会把正则每次捕获的内容替换成什么
})
// 需求:把每个单词的首字母替换为大写,wo-de-shi-jie 当成一个单词
var str='my name is wo-de-shi-jie,i am 8 years old,i am the world No1!';
var reg=/[a-zA-Z-]+/g;
str=str.replace(reg,function () {
    return arguments[0].slice(0,1).toUpperCase() + arguments[0].slice(1);
})
console.log(str);

# 时间格式化字符串

var str = '2017-11-07 16:37:00';
//=>'2017年11月07日 16时37分00秒'

//=>使用正则实现
//1、执行一次捕获操作,得到需要的六个结果
var reg = /^(\d{4})-(\d{1,2})-(\d{1,2})\s+(\d{1,2}):(\d{1,2}):(\d{1,2})$/g;
// str = str.replace(reg, function () {
//     var arg = arguments;
//     return arg[1] + '年' + arg[2] + '月' + arg[3] + '日 ' + arg[4] + '时' + arg[5] + '分' + arg[6] + '秒';
// });
str = str.replace(reg, '$1年$2月$3日 $4时$5分$6秒');//=>$1等价于第一个分组中获取的内容,类似于上面代码的arg[1]
console.log(str);//=>'2017年11月07日 16时37分00秒'

String.prototype.myformate = function () {
  var ary=this.match(/\d+/g);
  var temp=arguments[0]||'{0}年{1}月{2}日 {3}时{4}分{5}秒';
  var reg=/\{(\d)\}/g;
  temp=temp.replace(reg,function () {
    var value=ary[arguments[1]]||'0';
    return value.length<2?'0'+value:value;
  })
  return temp;
}
上次更新: 9/25/2022, 8:32:52 PM