面试官!让咱们聊聊正则

简述

正则表达式是处理字符串的利器,并提升工做效率,一个好的正则可以帮咱们省去几十甚至上百行代码。在工做中,也许你会见到在代码中出现不少正则处理字符串,也可能见到代码中毫无正则,缘由在于会正则的人每每处理字符串首先想到用正则去处理,不会的那必然用不少API处理。而且在面试的时候不少同窗每每挂在正则这里,因此对于前端正则是一个必备技能。这篇文章不光能教会你如何使用它,还能让你懂得其中的原理,在之后使用和面试中将作到毫无害怕。javascript

一.基础

要想玩转正则首先要明白三大元素:修饰符 元字符 量词
例:var reg = /^\d$/g 这是一个简单点正则,下面咱们来对三大元素一探究竟html

1.修饰符

  • g - global 全局匹配(只要有匹配成功的结果一直匹配直到没有为止)
  • i - ignoreCase 忽略大小写
  • m - multiline 多行匹配

2.元字符

元字符其中分为特殊元字符和普通元字符
普通元字符就是列入数组,字母等元素
经常使用特殊元字符以下:前端

  • \ 转义字符(把一个普通字符转变为有特殊意义的字符,或者把一个有意义的字符转变为普通字符)
  • . 除了\n(换行符)之外的任意字符
  • \d 匹配一个0~9之间的数字
  • \D 匹配一个非0~9之间的数字(大写与小写字母组合正好是相反含义
  • \w 匹配一个0~9或字母或_之间的一个字符
  • \s 匹配一个任意空白字符
  • \b 匹配一个边界符
  • x|y 匹配x或者y中的一个
  • [a-z] 匹配a-z中的任意一个字符
  • [^a-z] 匹配非a-z中的任意一个字符
  • [xyz] 匹配x或y或z中的一个字符
  • [^xyz] 和上方相反
  • () 整的小分组,匹配一个小分组(能够理解为大正则中的一个小正则)
  • ^ 以某一个元字符开始
  • $ 以某一个元字符结束
  • ?: 只匹配不捕获
  • ?= 正向确定预查
  • ?! 正向否认预查

3.量词

量词主要的做用是用来描述元字符出现的次数 以下:java

  • + 让前面的元字符出现一到屡次
  • ? 出现零到一次
  • * 出现零到屡次
  • {n} 出现n次
  • {n,} 出现n到屡次
  • {n,m} 出现n到m次

看到这里你们必定有些晕,这些稀奇古怪的符号,到底如何运用,下面将带你们去运用这些字符,在运用以前同窗们要作到牢记 牢记 牢记这些字符,想要玩转正则必需要的就是牢记他们,当你牢记这些字符的同时说明你已经入门了。面试

二.元字符

  • 1.开始元字符 ^正则表达式

    匹配启示位置的元字符,例:var reg = /^2/; 表示开头必须是2,若是开始元字符放在[]里面 例:[^]表示非中括号种状况 相反含义windows

  • 2.结束元字符 $数组

    匹配结束位置的元字符,例: var reg = /2$/; 表示结尾必须是2,若是是var reg = /^2$/; 表示只能为2,由于2只表明一个元字符。浏览器

  • 3.转义字符 \函数

    将特殊元字符转换为普通字符,例:var reg = /^2.3$/ 正常理解为匹配启示为2 结束为3中间是.的正则,可是在这里面.属于特殊元字符,意义是除了\n(换行符)之外的任意字符,因此不管是2.3/2+3/2s.3等等,只要是不·\n都匹配成功,因此为了这种需求就用到了转义字符\ 以下: var reg = /^2\.3$/ 将特殊元字符.转义为真正的.元素,再次匹配只有2.3才能匹配成功 以下思考:

    var reg1 = /^\d$/
    var reg2 = /^\\d$/
    var reg3 = /^\\\d$/
    var reg4 = /^\\\\d$/
    复制代码

    首先reg1 表示0-9之间的数字 因此0-9都能匹配成功
    reg2出现两个\ 不管是0-9 d \d都匹配失败,只有\\d才匹配成功,缘由在于第一个转义字符把第二个\转义为普通\,此时第一个\也表示普通字符,因此只有匹配到\\d才生效
    reg3出现三个\,此时你们必定会认为\\\d才是正确答案,非也,此时的正确答案是\\[0-9]意思是\和0-9中任意一个数字,缘由在于第一个\将第二个转义,因此变为\\ \d分为了两部分,\\为普通字符,而\d表示一个0~9之间的数字,因此正确答案是\\[0-9]
    reg4出现四个\ 不少同窗会认为正确答案必定是\\\[0-9], 很惋惜,正确的是\\\\d,缘由是当第一个把第二个转义为普通字符时,第三个又把第四个转义为普通字符,因此最后匹配为\\\\d
    看到这里相信有一部分小伙伴已经晕了,虽然在工做咱们不会玩这种操做,可是要深入理解转义字符,在正则中若是你直接写 var reg = /^$/会报错,由于正则中不能单独一个\出现,由于他是一个特殊元字符,须要写至少两个\, 例如: var reg = /^\\$/ 正确的匹配结果为'\\',因此牢记两个\表明真正的\,这里搞明白的同窗,相信转义字符已经彻底掌握。

  • 4.或者元字符 x|y

    这个元字符很好理解就是匹配x或者匹配y,没有什么难度,举个小例子:

    var reg = /2|3/
    // 匹配数字为2或者为3
    复制代码
  • 5.小分组 ()

    在上面同窗们会发现用了()这样的元字符,他主要的特色就是将括号内的正则规则当作一个总体,至关于一个小的独立的正则,主要处理以下例子中的问题

    var reg = /^18|19$/
    
    // 这个例子不少同窗可以了解是18或者19开头 可是结尾呢?真的只匹配19么? 其实并非
    // 正确的匹配除了18 19 还有18一、18九、819 这时候小分组就起到了做用以下
    
    var reg = /^(18|19)$/
    
    // 这里将18或19用()包裹起来,起到小分组的做用
    // 这样这个正则只匹配18开头结尾或者19而不是181和189
    复制代码
  • 6.分组引用 \n

    分组引用的概念就是能够引用大正则中指定的小正则规则,例如:

    var reg = /^([a-z])([a-z])\2([a-z])$/
    // 符合的字符串:book week http ...​
    复制代码

    具体上面的例子理解为\2表明着第二个小正则规则的彻底引用,就是和第二个小正则([a-z])同样,能够减小正则的复杂度和处理屡次重复规则

  • 7.匹配字符 []

    [xyz][^xyz][a-z][^a-z]下面举个小例子,便于同窗们理解

    var reg = /^[a-zA_Z0-9_]$/
    // 这个正则和等价于\w 就是匹配一个0~9或字母或_之间的一个字符
    // 而正则[xyz]中的xyz分别表明a-z、A_Z、0-9,xyz只是一个表明标识,能够有xyzhw各类组合
    // 就像这个例子中有下划线_同样四个的匹配
    // 有一个重点补充,在[]中的特殊元字符通常都表明自己含义,以下
    var reg = /^[.?+&]$/
    // 表明着匹配. .? ?+ ...等等
    复制代码
  • 8.边界符 \b

    匹配一个单词边界,也就是指单词和空格间的位置(边界主要是单词的左右两边) 例如:

    var reg = /er\b/
    // 能够匹配never中的er,可是不能匹配verb中的er
    var reg = /\b\w+\b/g
    // 能匹配字母数字和下划线与单词边界 'my blog is www.ngaiwe.com'
    // 能匹配 'my'、'blog'、'is'、'www'、'ngaiwe'、'com'
    复制代码
  • 9.只匹配不捕获 ?:

    下面举一个例子,可是涉及到捕获内容,若是同窗们不太明白,能够先跳过此处,看完下面捕获,再返回来看

    var reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|X)$/g
    var str = '110105199001220613'
    console.log(reg.exec(str))
    // 打印结果为 "110105199001220613", "110105", "1990", "01", "22", "1", "3"
    
    var reg = /^(\d{6})(?:\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|X)$/g
    // 打印结果为 "110105199001220613", "110105", "01", "22", "1", "3"
    // 会将第二个小分组只匹配,不捕获
    复制代码
  • 10.正向确定预查 ?=

    这个概念比较难理解,用于就是为了匹配一个元素后面是的元素是否符合相应规则,可是并不消耗这个规则,例子1:

    var reg = /windows(?=95|98|NT|2000)/
    var str1 = 'windows2000'
    var str2 = 'windowsxp'
    
    console.log(reg.test(str1))
    console.log(reg.test(str2))
    // str1 为true str2 为false
    
    console.log(reg.exec(str1))
    console.log(reg.exec(str2))
    // 能捕获到str1 而且捕获结果时windows 并无将2000也同时捕获
    // 说明正向预查只负责匹配相应规则
    复制代码

    例子2:

    var reg1 = /win(?=d)dows/
    var reg2 = /win(d)dows/
    var str = 'windows'
    console.log(reg1.test(str))
    console.log(reg2.test(str))
    // reg1 返回true reg2返回 false
    // 缘由是正向预查只负责匹配,不消耗字符,也就是并不会匹配为里面规则的字符
    // reg1 至关于匹配windows而且符合win后面第一个出现的是d
    // reg2 至关于匹配winddows
    复制代码
  • 11.正想否认预查 ?!

    和正向确定预查相反,匹配不符合规则的正则

三.正则的两种建立方式

正则的建立方式分为两种,一种是字面量建立,另外一种是构造函数建立

  • 1.字面量建立

var reg = /\d+/img

  • 2.构造函数建立

var reg = new RegExp('\\d+', 'img') 第一个参数是元字符而且\d这种特殊字符在这里面是普通字符,因此须要用\转义为特殊字符,第二个参数是修饰符

这两种的用途有区别,通常须要动态建立正则元字符采用构造函数建立,由于里面元字符是字符串拼接,常规固定写死正则采用字面量建立,例子以下:

var stringMode = 'string'
var reg = new RegExp(`^\\[object ${stringMode}\\]$`)
console.log(reg.toString())
复制代码

四.正则原型方法

首相上一张图让同窗们看一下正则一共有哪些方法,以下:

正则原型对象方法

能够看到正则原型对象上一共就三个方法,exec testtoString

  • 1.exec该方法主要应用于捕获组而设计,实参是要匹配的字符串。如图所示:

    正则原型对象方法

    捕获原理

    • 1.在捕获的时候先验证当前字符串和正则是否匹配,不匹配返回null(没有捕获到任何内容)
    • 2.若是匹配从字符串最左边开始,向右查找到匹配内容,并把匹配的内容返回

    捕获结果

    • 1.结果是一个数组
    • 2.第一项0 是当前本次大正则中匹配的结果
    • 3.index是匹配到的结果在字符串中的索引位置
    • 4.input当前正则操做的原始字符串
    • 5.若是大正则中有分组(),获取的数组中从第二项开始都是每一个小分组的捕获结果
    • 下面举一个身份证正则的例子 便于参考,具体里面匹配规则下面会单独介绍,这里只学习字段意义

    正则exec方法

  • 2.懒惰性

    正则捕获存在懒惰性,在上面exec中,执行一次exec只捕获到第一个符合规则的内容,第二次执行exec也是捕获到第一个内容,后面的内容不论执行多少次都没法捕获到

    解决方法

    在正则的末尾加修饰符g(全局匹配)

    原理

    如图所示,正则自己有一个属性 lastIndex 下一次正则在字符串中匹配查找的开始索引
    默认值是0,从字符串第一个位置开始查找,因而可知,当执行完exec后lastIndex并无变,而且就算手动修改lastIndex也不会起做用,浏览器不识别

    正则exec方法
    正则exec方法

    加上了修饰符g就解决了这个问题,由于每一次exec以后,浏览器默认会修改lastIndex,下一次从上一次结束的位置开始查找,因此能够获得后面内容

    正则exec方法
    正则exec方法

    咱们来手动建立一个函数来实现匹配到所有内容而且都捕获到,以下:

    var reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|X)$/g
    var str = '110105199001220613'
    
    RegExp.prototype.myExec = function myExec() {
    	var str = arguments[0] || ''
    	var result = []
    	// 首先this指向的是RegExp,因此判断this是否加了全局修饰符g
    	// 若是没有,防止执行死循环,咱们只执行一次exec并将其返回便可
    	
    	if(!this.global) {
    		return this.exec(str)
    	}
    	
    	var arrs = this.exec(str)
    	while(arrs) {
    		result.push(arrs[0])
    		// 此时lastIndex的值已经变为上一次的结尾
    		arrs = this.exec(str)
    	}
    	return result
    }
    复制代码

    这个方法当正则reg加了修饰符g 则返回大正则匹配到结果,若是没加g则返回exec捕获结果

  • 3.test 该方法主要应用于正则匹配,固然也能够用在捕获上面

    从字符串左侧开始匹配,匹配到符合正则规则的字符,返回true,不然返回false,一样的test在修饰符g下也会修改lastIndex的值

    运用test实现捕获

    var reg = /\{([a-z]+)\}/g
    var str = 'my name is {weiran}. I am from {china}'
    var result = []
    while (reg.test(str)) {
    	result.push(RegExp.$1)
    }
    console.log(result)
    // ['weiran', 'china']
    复制代码

    当test匹配到结尾或者匹配不到时,返回false,成功则向数组添加当前小分组匹配第一个元素内容 在RegExp的constructor中存在$1-$9,他们的具体指的是当前本次匹配小分组第一到第九捕获的内容

  • 4.toString

    就是将正则表达式转化为字符串

五.字符串正则方法

  • 1.match

    一样是捕获的方法,如图所示:

    正则match方法

    当加了修饰符g,返回的是大正则匹配结果组成的数组,不加修饰符g则返回大正则和每一个小分组返回结果组成的数组,跟上面咱们手写的myExec同样,其实原理就是咱们上面写的方法,若是想在加了修饰符g的时候返回结果和没加同样,因此在直接用match方法就能将匹配内容所有捕获到。 可是他也有局限性,就想上面说的在加了修饰符g的时候,会忽略小分组捕获内容,只捕获大正则捕获内容,解决办法就向上面myExec同样,将arrs[0]改成arrs,在每次匹配到结果时,将每一个小分组也保存下来。

  • 2.replace

    主要运用在替换,其中两个参数,第一个为要替换的字符串或者正则,第二个是替换内容或一个返回函数,具体操做以下:

    var str = 'my name is {weiran}, my blog is {ngaiwe}'
    var reg = /\{([a-z]+)\}/
    str = str.replace(reg, '123')
    console.log(str)
    // 打印出 my name is 123, my blog is {ngaiwe}
    // 同窗们会发现和exec的懒惰性很类似,不加修饰符g 只匹配第一个lastIndex没有改变
    
    var reg = /\{([a-z]+)\}/g
    // 打印出 my name is 123, my blog is 123
    复制代码

    而且replace不会修改原始字符串

    正则replace方法

    var str = 'my name is {weiran}, my blog is {ngaiwe}'
    var reg = /\{([a-z]+)\}/g
    str = str.replace(reg, function () {
    	console.log(arguments)
    })
    // 打印出当前匹配的小分组,若是函数中没有return出替换值,则返回undefined
    复制代码
  • 3.split

    按照正则方式能够拆分红数组,具体例子以下:

    var str = 'weiRanNgaiWe'
    var reg = /[A-Z]/
    console.log(str.split(reg))
    // ["wei", "an", "gai", "e"]按照大写拆分红数组
    复制代码
  • 4.search

    相似于indexOf,返回匹配元素的起始位置,若是没有返回-1,不支持修饰符g

    var str = 'ngaiwe@126.com'
    var reg = /\d+/
    console.log(str.search(reg))
    // 返回 7
    复制代码

六.实战案例剖析

  • 1.身份证号码

    举个身份证的例子: 110105199109214237
    由此分析前6位是数字地区区号组成,而后四位是年,两位月,两位日和四位随机,倒数第二位单数男性,双数女性,最后一位多是大写X,因此根据这个规则的正则是

    var str = '110105199109214237'
    var reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|X)$/
    console.log(reg.exec(str))
    // ["110105199109214237", "110105", "1991", "09", "21", "3", "7", index: 0, input: "110105199109214237", groups: undefined]
    复制代码
  • 2.邮箱

    举个邮箱的例子:weiran@vipkid.com.cn
    由此分析:
    1.@前面多是数字、字母、下划线、-、. 2.-和.不能相连在一块儿
    /^\w+((-|\w+)|(.\w+))*/ 开头必定是数字,字母或下划线组成,后面的内容多是-与数字字母下划线 或者.和数字字母下划线组成的0到多个字符
    3.@后面部分
    首先必定是数字字母组成的多位字符
    而后可能存在是.组成的邮箱后缀或者连接前方字符的.和-
    最后确定是.组成的邮箱后缀

    var reg = /^\w+((-|\w+)|(\.\w+))*@[a-zA-Z0-9]+((\.|-)[a-zA-Z0-9]+)*\.[a-zA-Z0-9]+$/
    var str = 'weiran@vipkid.com.cn'
    console.log(reg.test(str))
    // true
    复制代码
  • 3.URL截取

    举个截取url参数的例子:
    www.ngaiwe.com/page/index.…
    咱们想要的是参数转化键值对和哈希值{name: 'weiran',age: 27, sex: 0, HASH: 'develpoment'}
    由此分析:
    须要分为两部分捕获,首先第一次捕获?后面的参数,第二次捕获#后面的hash值
    首先匹配第一个,他的规则是匹配等号两边因此是/()=()/,而且匹配的是非?&=#的特殊字符,将他们保存在obj对象中
    其次匹配hash,方法和第一个相似只是匹配#后面的部分

    String.prototype.myQueryURLParameter = function myQueryURLParameter () {
    	var obj = {}
    	this.replace(/([^?&=#]+)=([^?&=#]+)/g, function () {
    		obj[arguments[1]] = arguments[2]
    	})
    	this.replace(/#([^?&=#]+)/g, function () {
    		obj['HASH'] = arguments[1]
    	})
    	return obj
    }
    复制代码

七.总结

具体案例就就少到这里,网上有各类正则的案例,同窗们之后看到案例具体分析他们是如何写的,不要一味地使用,这样才能在工做中不断地思考,学习。

八.博客

魏燃技术博客

有任何问题可留言或者发送本人邮箱ngaiwe@126.com

九.分享

正则元字符表
正则在线测试工具

相关文章
相关标签/搜索