js算法全归纳(二)字符串:JS正则表达式(实用全归纳)


正则表达式

正则表达式是匹配模式,要么匹配字符,要么匹配位置
正则表达式工具,常见简写形式:https://c.runoob.com/front-end/854


一、正则表达式的六种操作

RegExp 对象方法

1. 验证test

验证是否匹配成功,所谓匹配,就是看目标字符串里是否有满足匹配的子串,如果有位置符号限定,则去固定位置寻找子串(比如^p$,寻找的就是整个字符串是否匹配)。是正则对象的方法
返回值:
如果字符串 string 中含有与 RegExpObject 匹配的文本,则返回 true,否则返回 false。验证是正则表达式最直接的应用,比如表单验证。

RegExpObject.test(string)

var regex = /^(\d{4})-(\d{2})-(\d{2})$/; 
var string = "2017-06-12"; 
regex.test(string) //true
2. 检索匹配和捕获的子表达式exec()
RegExpObject.exec(string)

var regex = /(\d{4})-(\d{2})-(\d{2})/; 
var string = "2017-06-12"; 
regex.exec(string)
// ["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12", groups: undefined]
//0个元素为匹配字符串,1——n为括号子表达式,还有index和input属性

返回值:
返回一个数组,其中存放匹配的结果。如果未找到匹配,则返回值为 null。
返回一个数组对象,

  • 此数组的第 0 个元素是与正则表达式相匹配的字符串
  • 第 1 ~n个元素是与 RegExpObject 捕获的第 1 ~n个子表达式(如果有的话)还含有两个对象属性。
  • index 属性声明的是匹配文本的起始字符在 stringObject 中的位置,
  • input 属性声明的是对 stringObject 的引用。

String 对象的正则方法

1. 第一个匹配索引search()

search() 方法用于检索字符串中指定的子字符串,或正则表达式相匹配的子字符串位置

stringObject.search(regexp)

var regex = /(\d{2})-/; 
var string = "2017-06-12"; 
string.search(regex)//2 第一个匹配是‘17‘,索引为2

返回值:
stringObject 中第一个与 regexp 相匹配的子串的索引。search() 方法不执行全局匹配,它将忽略标志 g

2. 检索匹配和捕获的子表达式match()

match() 方法用于检索字符串中指定的子字符串,或正则表达式相匹配的子字符串以及子表达式

stringObject.match(searchvalue)
stringObject.match(regexp)

//没有g,匹配一次,获取子表达式,返回数组对象
var regex = /(\d{4})-(\d{2})-(\d{2})/; 
var string = "2017-06-12"; 
string.match(regex)
//["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12", groups: undefined]

//有g,全局匹配,忽略子表达式,返回纯数组
var regex = /(\d{4})-(\d{2})-(\d{2})/g; 
var string = "2017-06-12"; 
string.match(regex)
//["2017-06-12"]

返回值:
存放匹配结果的数组。该数组的内容依赖于 regexp 是否具有全局标志 g

  • 如果 regexp 没有标志 g,那么 match() 方法就只能在 stringObject 中执行一次匹配。返回一个数组对象,和exec方法返回一样:
    js算法全归纳(二)字符串:JS正则表达式(实用全归纳)
  • 如果regexp 有标志 g, match() 方法将执行全局检索,忽略括号的子表达式,返回一个数组,数组元素中存放的是 stringObject 中所有的匹配子串,而且也没有 index 属性或 input 属性。如果您需要这些全局检索的信息,可以使RegExp.exec()
    js算法全归纳(二)字符串:JS正则表达式(实用全归纳)
3. 替换字符/遍历所有匹配replace()

用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。

stringObject.replace(regexp/substr,replacement)

replacement 必需。规定了替换文本生成替换文本的函数(可以无返回)

  1. replacement是字符串:
  • 是普通字符串:直接返回替换后的string【全局/非全局】
  • $ 字符,则返回新的模板字符串,$从模式匹配得到的字符串将用于模版。
    • 【常用于非全局匹配,而且知道要操作的字符是哪几个捕获的子表达式,返回值可以直接由模版生成,不需要js操作
      js算法全归纳(二)字符串:JS正则表达式(实用全归纳)
      比如,想把 yyyy-mm-dd 格式,替换成 mm/dd/yyyy 怎么做?
var regex = /(\d{4})-(\d{2})-(\d{2})/; 
var string = "2017-06-12"; 
var result = string.replace(regex, "$2/$3/$1"); 
console.log(result); // => "06/12/2017"

"$2/$3/$1"就是新的模板。

  1. replacement是回调函数:/g全局搜索时每个匹配都调用该函数(相当于一个遍历),它返回的字符串将作为替换文本使用。
    • 【常用于全局匹配,而且需要对每一次匹配和子表达式进行操作,而且返回值要经过js操作
    • 该函数的第一个参数是匹配模式的字符串。
    • 接下来的参数是与模式中的子表达式匹配的字符串,可以有 0 个或多个这样的参数。
    • 接下来的参数是一个整数,声明了匹配在 stringObject 中出现的位置。
    • 最后一个参数是 stringObject 本身。
//函数语法 
function($&,$1,$2……$n,index,string){}

//通过正则表达式获取 多个url 参数
function getUrlParam(sUrl, sKey) {
  result = {};//用来存储参数键值对
  sUrl.replace(/\??(\w+)=(\w+)&?/g, function(str, key, value) {//对每一次的匹配遍历这个函数
   if (result[key] !== undefined) {//键值已定义
     var t = result[key];
      result[key] = [].concat(t, value);//把新元素拼接成一个数组
    } else {//键值未定义
      result[key] = value;//直接为对象创建这个新属性
    }
  });
}

如果回调函数没有返回值,就是没有定义要返回的字符串,那么返回的string不会被替换,是原始值。
该例子详见:https://blog.****.net/weixin_28900307/article/details/88193973

4. 分割成字符串数组split()

split() 方法用于把一个字符串分割成字符串数组。

stringObject.split(separator,howmany)//separator	必需。字符串或正则表达式,从该参数指定的地方分割 stringObject。
//howmany	可选。该参数可指定返回的数组的最大长度。

返回值:

  • separator 是不包含子表达式的正则表达式:一个字符串数组。该数组是通过在 separator 指定的边界处将字符串 stringObject 分割成子串创建的。返回的数组中的字串不包括 separator 自身。
  • 但是,如果 separator 是包含子表达式的正则表达式:那么返回的数组中包括与由匹配字符串分隔的子串,以及这些子表达式匹配的字串(但不包括与整个正则表达式匹配的文本)。
var regex = /(\d{4})-(\d{2})-(\d{2})/; 
var string = "2017-06-12"; 
string.split(regex)
//["", "2017", "06", "12", ""] 分隔子串+子表达式

二、正则表达式字符匹配

1. 两种模糊匹配

如果正则只有精确匹配是没多大意义的,正则表达式之所以强大,是因为其能实现模糊匹配。

1.1 横向模糊匹配

量词
即一个正则可匹配的字符串长度不固定,可以是多种情况。其实现的方式是使用量词。譬如 {m,n},表示连续出现最少 m 次,最多 n 次。
js算法全归纳(二)字符串:JS正则表达式(实用全归纳)

let r = /ab{2,5}c/g;
let s = "abc abbc abbbc abbbbbbc";
s.match(r); // ["abbc", "abbbc"]
1.1.1 总结:常用横向匹配的量词

普通量词
?等价于 {0,1},表示出现或者不出现。问号的意思表示,有吗?
* 等价于 {0,},表示出现至少0次。

+ 等价于 {1,},表示出现至少一次。加号是追加的意思。
{n} 表示出现 n 次。
{n,}表示至少出现 m 次。
{n,m} 表示连续出现最少 m~ n 次。

贪婪匹配与惰性匹配量词
贪婪匹配
普通量词都是贪婪的,比如 b{1,3},因为其是贪婪的,尝试可能的顺序是从多往少的方向去尝 试。首先会尝试 “bbb”,不能匹配时,再继续尝试bb。

本质上就是深度优先搜索算法。其中退到之前的某一步这一过程,我们称为“回溯”

如果当多个贪婪量词挨着存在,并相互有冲突时,前面的部分贪婪,后面的部分贪婪》回溯

var string = "12345"; 
var regex = /(\d{1,3})(\d{1,3})/;
console.log( string.match(regex) ); 
// => ["12345", "123", "45", index: 0, input: "12345"]

其中,前面的 \d{1,3} 匹配的是 “123”,后面的 \d{1,3} 匹配的是 “45”。

惰性匹配
? 量词后面加个问号就能实现惰性匹配,在匹配成功的前提下,尽可能少的匹配所搜索的字符串。

var string = "123456"; 
var regex = /(\d{1,3}?)(\d{1,3})/; 
console.log( string.match(regex) ); 
// => ["1234", "1", "234", index: 0, input: "12345"]

1.2 纵向模糊匹配

即一个正则可匹配某个不确定的字符,可以有多种可能。如 /[abc]/ 表示匹配 “a”, “b”, “c” 中任意一个。[123]表示匹配1,2,3任意一个。
js算法全归纳(二)字符串:JS正则表达式(实用全归纳)

let r = /a[123]b/g;
let s = "a0b a1b a4b";
s.match(r); // ["a1b"]
1.2.1 字符组(纵向模糊匹配的扩展)

虽叫字符组(字符类),但只是其中一个字符。例如 [abc],表示匹配一个字符,它可以是 “a”、“b”、“c” 之一

范围表示法

可以指定字符范围,比如 [1234abcdUVWXYZ] 就可以表示成 [1-4a-dU-Z] ,使用 - 来进行缩写。

排除字符组

即需要排除某些字符时使用,通过在字符组第一个使用 ^ 来表示取反,如 [^abc] 就表示匹配除了 "a", "b", "c" 的任意一个字符。

1.2.2 多选分支

如我用 /good|goodbye/,去匹配 “goodbye” 字符串时,结果是 “good”:分支结构也是惰性的,即当前面的匹配上了,后面的就不再尝试了。优先匹配第一个
注意分支h后面的符号会跟随分支,而不是整个表达式,如果需要请把分支括起来

1.2.3 总结: 常用纵向匹配的表达式

x|y 匹配 x 或 y。xy可以是字符也可以是位置。一般搭配括号使用,所以要注意是否需要用(?:pattern)这种非捕获括号。例如,‘z|food’ 能匹配 “z” 或 “food”。’(z|f)ood’ 则匹配 “zood” 或 “food”。
[xyz] [^xyz]字符集合。匹配所包含的任意一个字符。匹配未包含的任意字符。
[a-z] [^a-z]字符范围。匹配指定范围内的任意字符。匹配任何不在指定范围内的任意字符。
\d匹配一个数字字符。等价于 [0-9]。
\w匹配字母、数字、下划线。等价于'[A-Za-z0-9_]'。无特殊符号
\W匹配非字母、数字、下划线,也就是一些特殊字符,空格,换行。等价于 '[^A-Za-z0-9_]'。有特殊符号
.通配符,匹配除换行符(\n、\r)之外的任何单个字符,有特殊符号。
\n匹配一个换行符。等价于 \x0a 和 \cJ。
\s匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]
^、$、.、*、+、?、|、\、/、(、)、[、]、{、}、=、!、:、- 属于元字符,需要加\转义符转义


三、正则表达式位置匹配

位置(锚)是相邻字符之间的位置。比如,下图中箭头所指的地方:
js算法全归纳(二)字符串:JS正则表达式(实用全归纳)
所有的位置,我们可以理解成空字符 “”,把 /^helloKaTeX parse error: Expected group after '^' at position 7: / 写成 /^̲^hello$$/,是没有任何问题的:

var result = /^^hello$$$/.test("hello"); 
console.log(result); // => true

在 ES5 中,共有 6 个锚:^、$、\b、\B、(?=p)、(?!p)
1.^(脱字符)匹配开头,在多行匹配中匹配行开头。
2.$(美元符号)匹配结尾,在多行匹配中匹配行结尾。
多行匹配模式(即有修饰符 m)时,二者是行的概念

var result = "I\nlove\njavascript".replace(/^|$/gm, '#'); 
console.log(result); 
/* #I# #love# #javascript# */

3.\b 是单词边界,具体就是 \w 与 \W (特殊符号,空格 )之间的位置,也包括 \w 与 ^字符串以单词开头位置) 之间的位置,和 \w 与 $字符串以单词结尾结尾)之间的位置。
\B 就是 \b 的反面的意思,非单词边界。

var result = "[JS] Lesson_01.mp4".replace(/\b/g, '#'); 
console.log(result); 
// => "[#JS#] #Lesson_01#.#mp4#"
var result = "[JS] Lesson_01.mp4".replace(/\B/g, '#'); 
console.log(result); 
// => "#[J#S]# L#e#s#s#o#n#_#0#1.m#p#4"

4.(?=p)正向肯定预查(look ahead positive assert),它代表着:

  1. p 前面的位置,可以定位到这个位置,用replace方法去插入指定字符;
  2. 同样也可以是一个条件在该位置后面必须有p也就是给后面要匹配的字符串,加上了括号里面的条件:要匹配的字符串里面要包含p,具体位置看具体写法
    (?!p) 正向否定预查(negative assert),就是 (?=p)反面意思
var result = "hello".replace(/(?=l)/g, '#'); 
console.log(result); 
// => "he#l#lo"
var result = "hello".replace(/(?!l)/g, '#'); console.log(result); // => "#h#ell#o#"

5.(?<=p)p 反向(look behind)肯定预查,它代表着:

  1. p 后面的位置,可以定位到这个位置,用replace方法去插入指定字符;
  2. 同样也可以是一个条件在该位置前面必须有p
    (?<!p)反向否定预查,就是 (?<=p)反面意思
var result = "hello".replace(/(?<=l)/g, '#'); 
console.log(result); 
// => "hel#l#o"
var result = "hello".replace(/(?<!l)/g, '#'); console.log(result); 
// => "#h#e#llo#"

注意:所有预查必须带括号


四、括号分组引用

4.1 括号捕获子表达式

在匹配过程中,给每一个**(分组)都开辟一个空间,用来存储每一个分组匹配到的 数据。捕获到的数据和匹配的表达式是不一样的。匹配的字符串是match的首元素,捕获到的数据是match剩下的元素,或者通过replace的$1234获取,
分组后面有
量词**,分组最终捕获到的数据$1是最后一次的匹配。/(\d)+/匹配"12345"捕获得到5

"12345abc"匹配/(\d)+/得到12345(有追加,贪心匹配最长),捕获得到最后一个内容(最后一个追加)5
"12345abc"匹配/(\d)/得到1(无追加,无全局g,匹配第一个),捕获得到第一个内容1
js算法全归纳(二)字符串:JS正则表达式(实用全归纳)
用法一:match提取多组子串
分组结合match方法常用来从一个字符串中,提取多组子串,比如提取年、月、日

var regex = /(\d{4})-(\d{2})-(\d{2})/; 
var string = "2017-06-12"; console.log( string.match(regex) );
 // => ["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12"]

同时,regex是一个RegExp的实例,所以也可以使用RegExp构造函数的全局属性 $1 至 $9 来获取:

var regex = /(\d{4})-(\d{2})-(\d{2})/; 
var string = "2017-06-12";
regex.test(string); // 正则操作即可,例如 //regex.exec(string); //string.match(regex);
console.log(RegExp.$1); // "2017" 
console.log(RegExp.$2); // "06" 
console.log(RegExp.$3); // "12"

用法二:repalce操作替换子串
想把 yyyy-mm-dd 格式,替换成 mm/dd/yyyy:

//repalce第二个参数是一个函数,匹配字符串作为参数传进去
var regex = /(\d{4})-(\d{2})-(\d{2})/; 
var string = "2017-06-12"; 
var result = string.replace(regex, function (match, year, month, day) { 
return month + "/" + day + "/" + year; 
}); 
console.log(result); // => "06/12/2017"
//repalce第二个参数是一个函数,匹配字符串通过正则构造函数获取
var regex = /(\d{4})-(\d{2})-(\d{2})/; 
var string = "2017-06-12"; 
var result = string.replace(regex, function () { 
return RegExp.$2 + "/" + RegExp.$3 + "/" + RegExp.$1;
 }); 
console.log(result); // => "06/12/2017"

最方便的方法:

//repalce第二个参数是一个字符串
var regex = /(\d{4})-(\d{2})-(\d{2})/; 
var string = "2017-06-12"; 
var result = string.replace(regex, "$2/$3/$1"); 
console.log(result); // => "06/12/2017"

4.2 反向引用

引用之前出现的分组
\num在正则本身里引用之前出现的分组,比如要写一个正则支持匹配如下三种格式:
2016-06-12 2016/06/12 2016.06.12
但是又不想匹配 “2016-06/12” 这样的数据。

var regex = /\d{4}(-|\/|\.)\d{2}\1\d{2}/; 

保证了两次的连接符号是一致的
反向引用的注意点

  • \10 表示什么呢?\10 是表示第 10 个分组,
    如果真要匹配 \1 和 0 的话,请使用 (?:\1)0 或者 \1(?:0)
var regex = /(1)(2)(3)(4)(5)(6)(7)(8)(9)(#) \10+/;
 var string = "123456789# ######" 
 console.log( regex.test(string) ); // => true
  • 括号嵌套怎么办?以左括号(开括号)为准。

4.3 非捕获括号

(?:pattern)匹配表达式里面只是想要单纯的用括号,匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用,不需要被反向引用。这在使用 "或字符 (|) 来组合一个模式的各个部分是很有用。例如,
'industr(?:y|ies) 就是一个比 'industry|industries' 更简略的表达式。
一般来说,别的括号没有用到捕获分组的时候,不需要加?:来限定非捕获。

参考 http://www.w3school.com.cn/jsref/jsref_obj_regexp.asp
https://zhuanlan.zhihu.com/p/59469237