正则表达式不管是前后端开发,都是很容易碰到的痛点,就像前女友一样时不时的让你这么痛一下,最近抽空重新学习了下js里面的正则表达式,随记之。

正常水平本文阅读需要10分钟即可,10分钟回顾知识点巩固一下基础。

一:理论部分

1. 实例化两种方式

 var reg = /\bis\b/g;  // 自变量
 var reg = new RegExp('\\bis\\b','g');  // 对象实例化方式,好处是可以传递变量进来

2. 修饰符 或者叫做 对象只读属性

global:是否全文搜索,默认false  reg.global

ignoreCase:是否大小写敏感,默认是false  reg.ignoreCase

multiline:多行搜索,默认值是false

lastIndex:当前表达式匹配内容的最后一个字符的下一个位置

source:正则表达式的文本字符串

这些属性我们可以在正则对象的实例化对象身上看的到,也是我们正则表达式的修饰符号。

3. 常见的表达式模式

[abc]    查找方括号之间的任何字符。
[0-9]    查找任何从 0 至 9 的数字。
(x|y)    查找任何以 | 分隔的选项。

4. 元字符

(1)**原义文本字符**:代表它原来含义的字符 例如:abc、123
(2)**元字符**:在正则表达式中有特殊意义的非字母字符 例如:\b表示匹配单词边界,
(3) 在正则表达式中具体特殊含义的字符:* + ? $ ^ . \ () {} []

5. 字符类

一般情况下正则表达式一个字符对应字符串一个字符, 表达式 ab\t 的含义是: 一个字母a一个字母b加一个水平制表符 \r 回车 \n 换行。

可以使用元字符[]来构建一个简单的【类】,所谓类是指符合某些特征的对象,一个泛指,而不是特指某个字符,例如表达式[abc]:把字符 a 或 b 或 c 归为一类,表达式可以匹配这类的字符,即匹配abc中的,但是 [^abc] 是取反的意思。

6. 范围类

正则表达式提供了范围类,可以使用[a-z]来连接两个字符类表示从a到z的任意字符,这是一个闭区间,也就是包含a和z;
可以进行连写:[a-zA-Z],但是如果同时范围内包含字符"-":如[0-9-] :"2012-08-08".replace(/[0-9-]/,'');

7: 预定义类 匹配常见的字符类

.  等价于 [^\r\n] 表示除了回车符和换行符之外的所有的字符

\d 等价于 [0-9] 数字字符 d:digit

\D [^0-9] 非数字字符

\s 等价于 [\t\n\x0B\f\r] 空白符 s:space

\S 非空白符号

\w 等价于 [a-zA-Z_0-9] 单词字符(字母、数字下划线) w:word


边界匹配
  ^ 以XXX开始
  $ 以XXX结束
  \b 单词边界
  \B 非单词边界

8 量词: 作用于紧挨着的字母,并不是整个单词,需要分组才能拿到整个单词

? 出现0或1次(最多1次)

+  出现1或多次(至少1次)

*  出现0或多次(任意次)

{n}  出现n次

{n,m}  出现n到m次

{n,}  至少出现n次

量词放在元字符后面,如\d+

9:两种模式

**贪婪模式**:尽可能多地匹配,直到匹配失败 即匹配过程中有多个条件符合的话,会选择最多的那一种

**非贪婪模式**:让正则表达式尽可能少的匹配,一旦成功匹配则不再继续尝试,**在【量词】后面加上【?】即可**


 eg: '12345678'.replace(/\d{3,6}/g,'X')  "X78"贪婪模式:尽可能多的匹配

    '12345678'.replace(/\d{3,6}?/g,'X') "XX78"非贪婪模式:尽可能少的匹配

10 分组

(1) 使用()可以达到分组的功能,使量词作用于分组 (Byron){3},如果直接Byron{3}则匹配到的三Byronnn。 默认是贪婪的捕获性质的分组。

 例子:小写字母连续出现3次
'a1b2c3d4'.replace(/[a-z]\d{3}/g,'Q');  //"a1b2c3d4"
'a1b2c3d4'.replace(/([a-z]\d){3}/g,'Q'); //"Qd4"


// 通过$1 _ 取到捕获到的值。
var str = 'hello world';            
var pattern = /([a-z]+)\s([a-z]+)/; 
console.log(RegExp.$1) //'hello' 第一个分组([a-z]+)的值


// 捕获性的分组
var str1 = '000aaa111';             
var pattern = /([a-z]+)(\d+)/; //捕获性分组匹配
var arr = pattern.exec(str1);  
console.log(arr) // ['aaa111','aaa','111']   结果子串也获取到了,这并不是我们想要的结果

(2). 使用 将正则表达式分成前后两部分 【或 |】

 'ByrCasperByronsper'.replace(/Byr(on|Ca)sper/g,'Q'); // "QQ"

(3).反向引用 反向引用,捕获必须是在分组的基础之上进行操作的,后续案例中还要补充这个点。

例如 2015-12-25 => 12/25/2015 在分组的基础上,分组取值使用'$1 $2....'代表捕获分组内容。

(4). 非捕获性的分组,不希望捕获某些分组,只需要在分组内加上 ?:即可

例如'2015-07-09'.replace(/(?:\d{4})-(\d{2})-(\d{2})/g,'$2/$3/$1'); // "09/$3/07"

//非捕获性分组
var str2 = '000aaa111';
var pattern2 = /(?:[a-z]+)(?:\d+)/; //非捕获性分组匹配
var arr2 = pattern2.exec(str2);  
console.log(arr2) //['aaa111']  结果正确  

11 前瞻

js 不支持后顾 eg: 匹配到张三,而且还要看看他爹是不是叫李四

正则表达式从文本头部向尾部开始解析,文本尾部方向为“前”,头部方向为“后”

前瞻:正则表达式匹配到规则的时候,向前检查是否符合断言,后顾/后瞻方向相反(javascript不支持)

 符合断言:肯定/正向匹配      正向前瞻:exp(?=assert)
 不符合断言:否定/负向匹配    负向前瞻:exp(?!assert)

很重要的一点是, 前瞻中断言只作为判断条件,不参与规则部分的操作

12 正则相关的一些方法

  test 不支持全局匹配
   RegExp.prototype = {

        test: 不支持全局匹配  re.test(str)  重点在于检测 返回true/false
        : 返回数组【匹配项目,有子正则的话输出子匹配项目,具体哪一个开始匹配index】
   }

   String.prototype = {
        search: 找不到返回-1  str.seach(re);
        match: 返回匹配到的数组  str.match(re)  如果没有g只会匹配一次
        replace:
        split
   }

二:实战案例

**(1) is替换**

const s = 'this is a book'.replace(/\bis\b/,'~');  // "this ~ a book"
const s1 = 'this is a book'.replace(/\Bis\b/,'~');  // "th~ is a book"
**(2) 把http 并且结尾是jpg的url 删除协议头,即返回//...jpg** 

https://cdn.baidu.com/images/asdas.jpg

http://cdn.hexun.com/images/asdas.jpg

https://cdn.baidu.com/images/asdas.png

http://cdn.sohu.com/images/asdas.jpg

https://cdn.baidu.com/images/asdas.jpg

http://cdn.baidu.com/images/asdas.png

https://cdn.baidu.com/images/asdas.png

正则:`/^http:(\/\/.+\.jpg)$/gi`

(3) 把下面是日期的全部替换为: 月-日-年 

02-03-2006

test/07/sd

05-10-2015

16555/12/45

1253645/2131/34

05-15-1998


正则:str.replace( /^(\d{4})[/-](\d{2})[/-](\d{2})$/ $2-$3-$1)
(4)

// 正向前瞻 
const s4 = 'v3asd7*5sd'.replace(/\w(?=\d)/g,'~');  // "~3as~7*5sd"

// 负向前瞻  注意匹配项  是分组的结果 注意为什么没有使用全局匹配?
const s5 = 'v3asd7*5sd'.replace(/\w(?!\d)/,'~');  // "v~asd7*5sd"
(5)

// 非捕获性的分组是不会作为匹配项返回的
const str_0 = 'window 98 is ok 11';
const re_0 = /window (?=\d)/g; // 干脆不会作为结果出现
const re_1 = /window (?:\d+)/g; // 非捕获性分组定义子表达式可以作为整体被修饰但是子表达式匹配结果不会被存储。
const re_2 = /window (\d+)/g;  // 会作为匹配项目出现在子

console.log( re_0.exec(str_0) );
console.log( re_1.exec(str_0) );
console.log( re_2.exec(str_0) );


结果是:
[ 'window ', index: 0, input: 'window 98 is ok 11' ]
[ 'window 98', index: 0, input: 'window 98 is ok 11' ]
[ 'window 98', '98', index: 0, input: 'window 98 is ok 11' ]
(6)
var str_img = "img1.jpg,img2.jpg,img3.bmp";
var str_re_1 = /(?:\w+)(?=\.jpg)/g;
var str_re_2 = /(?:\w+)(?:\.jpg)/g;


console.log( str_img.match(str_re_1) )  //[ 'img1', 'img2' ]
console.log( str_img.match(str_re_2) )  // [ 'img1.jpg', 'img2.jpg' ]

// 上面使用的是match但是看下面代码执行的结果
console.log( str_re_1.exec(str_img) );  输出结果[ 'img1', index: 0, input: 'img1.jpg,img2.jpg,img3.bmp' ] 为什么不是全部的呢?

再看一下代码
var result;
while ( (result= str_re_1.exec(str_img)) !=null ){
   console.log(str_re_1.lastIndex)
   console.log(result)
}

>> 执行的结果是:

4
[ 'img1', index: 0, input: 'img1.jpg,img2.jpg,img3.bmp' ]
13
[ 'img2', index: 9, input: 'img1.jpg,img2.jpg,img3.bmp' ]


总结出,exec 不是全局的匹配结果

(7)手机号码中间4位 ****
console.log(
  '15201095029'.replace(/(?:\d{4})(?=\d{4}$)/g,function($,$1){
      return '****'
  })
);
(8)转换为驼峰的写法
console.log(

  'app-com-up'.replace(/-(\w)/g,function($,$1){
     return $1.toUpperCase()
  })
);

(9)贪婪匹配
  .+是非贪婪匹配   
  .+?是贪婪匹配
// 如,把用户输入的特殊字符进行转义,以避免xss攻击
  function htmlEscape(text) {
    return text.replace(/[<>"&]/g, function (match, pos, originalText) {
      switch (match) {
        case '<':
          return '&lt;'
        break;
        case '>':
          return '&gt;'
        break;
        case '\"':
          return '&quot;'
        break;
        case '&':
          return '&amp;'
        break;
      }
    })
  }

骑着洋葱撞地球
4 声望1 粉丝

搬砖、砌墙、打炮、打飞机、抗震动棒等一些列高复杂任务工作!总之,很浪很浪的那种浪!