正则,就是用来处理字符串的一种规则。

  • 正则只能用于处理字符串
  • 处理一般包含两个方面
    • 验证当前字符串是否符合某个规则,正则匹配
    • 把一个字符串中国符合规则的字符获取到,正则捕获
      正则的使用跟字符串是分不开的,使用正则有两种方法:
  • 使用正则实例的方法,需要传入字符串
  • 使用字符串实例的方法,需要传入正则

1. 正则的方法

test 匹配

查找这个字符串是否满足正则规律,匹配成功返回 true,否则返回 false
(1)正则中没有全局匹配时,每次都是从开头匹配,只要有符合的就返回 true

  1. var reg = /\d{2}/;
  2. var str = 'a11a';
  3. reg.test(str); //=> true

(2)正则中有全局匹配的时候,每次都会从 reg.lastIndex 索引开始匹配,只要匹配到后面都没有,则返回 false,此时索引会重置。与 exec 方法一样。

  1. var reg = /\d{2}/g;
  2. var str = 'a11a11';
  3. reg.test(str); //=> true
  4. reg.test(str); //=> true
  5. reg.test(str); //=> false

exec 捕获

在字符串中获取满足正则规律的字符串,匹配成功则返回一个数组,没有匹配到则返回 null
返回一个数组,数组中第一项是匹配的字符串,后面的项是匹配到的分组。
其中还有两个属性:

  • input:源字符串
  • index:匹配到的开始索引(从0开始)
    (1)正则中没有全局匹配的时候
  • 无论执行多少次,都是从头开始匹配
  • 都只能获取到第一个匹配,以及匹配的分组。
  1. var reg = /\d+/;
  2. var str = 'aa2018aa2019';
  3. reg.exec(str); //=> 2018
  4. reg.exec(str); //=> 2018

(2)正则中有全局匹配的时候

  • 每次执行的时候,会从上一次记录的 reg.lastIndex 索引开始查找,也就是每次查找的开始位置不一样
  • 即使再次使用该方法传入的字符串不一样,也会从上一次记录的 reg.lastIndex 索引开始查找
  • 每次使用这个方法,依然只获取第一次匹配到的字符串以及其中的分组
  1. var reg = /\d+/g;
  2. var str = 'aa2018aa2019';
  3. var str2 = 'aa2018aa2019';
  4. reg.exec(str); //=> 2018
  5. reg.exec(str2); //=> 2019,说明也是从上一次索引开始查找的
  6. reg.exec(str); //=> null
  7. reg.exec(str2); //=> 2018

需要注意的是:
> 1. 当它遍历到最后,再使用此方法,会返回有一个 null,此时 reg.lastIndex 也会重置回 0,之后又会从开头重新遍历
> 2. 使用同一个正则,那么其记录的 reg.lastIndex 是一样的,与字符串无关,这样就会出现不可预料的结果
> 3. 使用同一个正则的 test 或 exec 方法,也是相同的 reg.lastIndex,就会相互影响,结果不可预料
由于 exec 没有办法一次捕获所有匹配的字符串,同时也存在检测不同字符时,不是从开头匹配的 bug
所以,需要自己写一个 execAll 方法,捕获了所有的匹配字符串,并且最终索引都会回到最初。

  1. RegExp.prototype.execAll = function (str) {
  2. var temp;
  3. //=> 防止后面出现死循环,若没有全局修饰符,直接给它返回匹配到的第一个
  4. if(!this.global) {
  5. temp = this.exec(str);
  6. temp.errorReason = "你没有添加全局修饰符";
  7. return temp;
  8. }
  9. var ary = [];
  10. temp = this.exec(str);
  11. while (temp) { //=> 当 exec 没有捕获到时,返回 null,同时索引置回最初
  12. ary.push(temp[0]);
  13. temp = this.exec(str);
  14. }
  15. return ary;
  16. }

2. 字符串的方法

match 匹配

match 方法用于确定原字符串是否匹配某个子字符串,返回一个数组,成员为匹配的第一个字符串。如果没有找到匹配,则返回 null

  1. 'cat, bat, sat, fat'.match('at') // ["at"]
  2. 'cat, bat, sat, fat'.match('xt') // null

返回数组还有 index 属性和 input 属性,分别表示匹配字符串开始的位置和原始字符串。

  1. var matches = 'cat, bat, sat, fat'.match('at');
  2. matches.index // 1
  3. matches.input // "cat, bat, sat, fat"

match 方法还可以使用正则表达式作为参数,行为在很大程度上有赖于 regexp 是否具有标志 g
(1)如果 regexp 没有标志 g,就只能在源字符串中执行一次匹配。与 exec 一样

  • 如果没有找到任何匹配的文本, 返回 null
  • 否则,它将返回一个数组,其中存放了与它找到的匹配文本有关的信息。
    • 该数组的第 0 个元素存放的是匹配文本,而其余的元素存放的是正则的分组捕获到的内容
    • 返回的数组还含有两个对象属性。index 属性声明的是匹配文本的起始字符在源字符串中的位置,input 属性声明的是对源字符串的引用。
      (2)如果 regexp 具有标志 g,将执行全局检索,找到源字符串中的所有匹配子字符串。
  • 若没有找到任何匹配的子串,返回 null
  • 如果找到了一个或多个匹配子串,则返回一个数组。
    • 全局匹配返回的数组的内容与前者大不相同,它的数组元素中存放的是源字符串中所有的匹配子串,不再捕获分组
    • 没有 index 属性或 input 属性。
      > 注意:在全局检索模式下,match() 不提供与子表达式匹配的文本的信息,也不声明每个匹配子串的位置。如果您需要这些全局检索的信息,可以使用 exec()

split 分割

split 方法把字符串按照指定字符串进行分割成数组。传入的参数也可以是正则表达式。
传入参数是正则表达式的时候,会按照正则匹配到的字符串进行分割。
不管有没有全局匹配,都是按照全局搜索去匹配的。

  1. var reg = /[,+-]/;
  2. var str = 'aa,bb+cc-aa';
  3. str.split(reg); //=> ["aa", "bb", "cc", "aa"];
  4. var reg = /[,+-]/g;
  5. var str = 'aa,bb+cc-aa';
  6. str.split(reg); //=> ["aa", "bb", "cc", "aa"];

replace 替换

replace 用于将某个字符串替换成另外一个字符串,只会替换第一个。传入的第一个参数可以是正则表达式,此时会替换掉匹配到的字符串
第一个参数是正则时:

  • 如果 regexp 具有全局标志 g,那么将替换所有匹配的子串。
  • 否则,它只替换第一个匹配子串。
    当正则中没有全局匹配时,只会匹配到第一个,并且让替换它
  1. var reg = /\d+/;
  2. var str = "aa2018aa2019";
  3. str.replace(reg, ','); //=> "aa,aa2019"
  4. var reg = /\d+/g;
  5. var str = "aa2018aa2019";
  6. str.replace(reg, ','); //=> "aa,aa,"

第二个参数可以是字符串,也可以是函数:

  • 如果它是字符串,那么每个匹配都将由字符串替换。但是替换字符串中的 $ 字符具有特定的含义。如下表所示,它说明从模式匹配得到的字符串将用于替换。
    |字符|替换文本|
    |:—-JS 正则的使用 - 图1:—-JS 正则的使用 - 图2
    |$1、$2、…、$99| 与 regexp 中的第 1 到第 99 个子表达式相匹配的文本。|
    |$& |与 regexp 相匹配的子串。|
    |$` |位于匹配子串左侧的文本。|
    |$’|位于匹配子串右侧的文本。|
    |$$ |直接量符号。|
  • 如果是函数,每次匹配到内容都调用一次该函数,它返回的字符串将作为替换文本使用。
    • 该函数的第一个参数是匹配模式的字符串
    • 接下来的参数是与模式中的子表达式匹配的字符串,可以有 0 个或多个这样的参数
    • 接下来的参数是一个整数,声明了匹配在源字符串中出现的位置
    • 最后一个参数是源字符串本身
      每一次 ARG 中存储的信息。和执行 exec 捕获的信息相似(内置原理:每一次正则匹配到的结果,都把函数执行,然后基于 exec 把本次匹配的信息捕获到,然后把捕获的信息传递给这个函数)
  1. var reg = /(\d+)/g;
  2. var str = "aa2018aa2019";
  3. var str2 = str.replace(reg2, function() {
  4. consoel.log(arguments);
  5. //=> 第一项:匹配到的内容 str
  6. //=> 第二项:分组匹配到的内容,不只一项 str1,str2,str3...
  7. //=> 第三项:匹配内容的位置 index
  8. //=> 第四项:源字符串本身
  9. //=> return 返回的字符串用以替换匹配到的内容
  10. })

把字符串中的数字加 1

  1. var reg = /\d+/g;
  2. var str = "aa2018aa2019";
  3. var str3 = str.replace(reg, function() {
  4. var temp = arguments[0];
  5. temp = temp * 1 + 1; //=> 这里的 *1 将前面的字符串转换为数字
  6. return temp;
  7. })

把字符串中的数字每一位都加 1

  1. var reg = /\d/g; //=> 这里没有 +
  2. var str = "aa2018aa2019";
  3. var str3 = str.replace(reg, function() {
  4. var temp = arguments[0];
  5. temp = temp * 1 + 1;
  6. return temp;
  7. })

3. 正则的懒惰性

正则的捕获有懒惰性:只能捕获到第一个匹配的内容,剩余的默认捕获不到。
正则的懒惰性与正则的 lastIndex 属性有关,它代表正则捕获的时候,下一次在字符串中开始查找的索引。

  1. //=> lastIndex 不变,导致了正则捕获的懒惰性
  2. let str = 'aa18bb19';
  3. let reg = /\d+/;
  4. reg.lastIndex; //=> 0
  5. reg.exec(str); //=> ['18']
  6. reg.lastIndex; //=> 0
  7. reg.exec(str); //=> ['18']
  8. //=> 手动改变 lastIndex 值,不起作用
  9. reg.lastIndex = 4;
  10. reg.lastIndex; //=> 4
  11. reg.exec(str); //=> ['18']
  12. //=> 需要使用全局匹配
  13. let str = 'aa18bb19';
  14. let reg2 = /\d+/g;
  15. reg2.lastIndex; //=> 0
  16. reg2.exec(str); //=> ['18']
  17. reg2.lastIndex; //=> 4
  18. reg2.exec(str); //=> ['19']
  19. reg2.lastIndex; //=> 8
  20. reg2.exec(str); //=> null
  21. reg2.lastIndex; //=> 0
  22. reg2.exec(str); //=> ['18']

解决正则捕获的懒惰性,需要添加全局修饰符 g,这个是唯一的方法,而且不加 g,不管用什么办法捕获,也都不能把全部匹配捕获到。

4. 正则的贪婪性

出现量词的时候,每次匹配捕获的时候,总是捕获到和正则匹配中最长的内容。

  1. let str = 'aa2018';
  2. let reg1 = /\d+/g;
  3. reg1.exec(str); //=> '2018'
  4. //=> 把问号放在量词元字符后面,代表的就不是出现零次或者一次了,而是取消捕获的贪婪性
  5. let reg2 = /\d+?/g;
  6. reg2.exec(str); //=> '2'