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
方法返回一样: - 如果regexp 有标志 g, match() 方法将执行全局检索,忽略括号的子表达式,返回一个数组,数组元素中存放的是 stringObject 中所有的匹配子串,而且也没有 index 属性或 input 属性。如果您需要这些全局检索的信息,可以使
RegExp.exec()
。
3. 替换字符/遍历所有匹配replace()
用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。
stringObject.replace(regexp/substr,replacement)
replacement
必需。规定了替换文本或生成替换文本的函数(可以无返回)。
-
replacement
是字符串:
- 是普通字符串:直接返回替换后的string【全局/非全局】
- 有
$ 字符
,则返回新的模板字符串,$
从模式匹配得到的字符串将用于模版。- 【常用于非全局匹配,而且知道要操作的字符是哪几个捕获的子表达式,返回值可以直接由模版生成,不需要js操作】
比如,想把 yyyy-mm-dd 格式,替换成 mm/dd/yyyy 怎么做?
- 【常用于非全局匹配,而且知道要操作的字符是哪几个捕获的子表达式,返回值可以直接由模版生成,不需要js操作】
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"
就是新的模板。
-
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 次。
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任意一个。
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]
。^、$、.、*、+、?、|、\、/、(、)、[、]、{、}、=、!、:、-
属于元字符,需要加\转义符转义
三、正则表达式位置匹配
位置(锚)是相邻字符之间的位置。比如,下图中箭头所指的地方:
所有的位置,我们可以理解成空字符 “”,把 /^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),它代表着:
- p 前面的位置,可以定位到这个位置,用replace方法去插入指定字符;
- 同样也可以是一个条件:在该位置后面必须有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)肯定预查,它代表着:
- p 后面的位置,可以定位到这个位置,用replace方法去插入指定字符;
- 同样也可以是一个条件:在该位置前面必须有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
用法一: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