前端js正则表达式

49 篇文章 0 订阅
10 篇文章 0 订阅

正则表达式

第一章 正则表达式字符匹配攻略

1.1. 两种模糊匹配

1.1.1. 横向模糊匹配

一个正则可匹配的字符串的长度不是固定的,可以是多种情况的。譬如 {m,n},表示连续出现最少 m 次,最多 n 次。其实现的方式是使用量词。例如let reg = /ab{2,5}c/;

var regex = /ab{2,5}c/g;
var string = "abc abbc abbbc abbbbc abbbbbc abbbbbbc";
console.log( string.match(regex) );
// => ["abbc", "abbbc", "abbbbc", "abbbbbc"]

g 是正则的一个修饰符。表示全局匹配,即,在目标字符串中按顺序找到满足匹配模式的所有子串,强调的是“所有”,而不只是“第一个”.

1.1.2. 纵向模糊匹配

纵向模糊指的是,一个正则匹配的字符串,具体到某一位字符时,它可以不是某个确定的字符,可以有多种 可能。

其实现的方式是使用字符组。譬如 [abc],表示该字符是可以字符 “a”、“b”、“c” 中的任何一个。

比如 /a[123]b/ 可以匹配如下三种字符串: “a1b”、“a2b”、“a3b”。

var regex = /a[123]b/g;
var string = "a0b a1b a2b a3b a4b";
console.log( string.match(regex) );
// => ["a1b", "a2b", "a3b"]

1.2. 字符组

需要强调的是,虽叫字符组(字符类),但只是其中一个字符。

例如 [abc],表示匹配一个字符,它可以是 “a”、“b”、“c” 之一。

#### 1.2.1. 范围表示法

如果字符组里的字符特别多的话,怎么办?可以使用范围表示法。

比如[123456abcdefGHIJKLM],可以写成 [1-6a-fG-M]。用连字符- 来省略和简写。

因为连字符有特殊用途,那么要匹配 “a”、"-"、“z” 这三者中任意一个字符,该怎么做呢?

不能写成 [a-z],因为其表示小写字符中的任何一个字符。

可以写成如下的方式:[-az] 或 [az-] 或 [a\-z]

即要么放在开头,要么放在结尾,要么转义。总之不会让引擎认为是范围表示法就行了。

#### 1.2.2. 排除字符组

纵向模糊匹配,还有一种情形就是,某位字符可以是任何东西,但就不能是 “a”、“b”、“c”。

此时就是排除字符组(反义字符组)的概念。

例如**[^abc]**,表示是一个除 “a”、“b”、"c"之外的任意一个字 符。字符组的第一位放 ^(脱字符),表示求反的概念。 当然,也有相应的范围表示法。

1.2.3. 常见的简写形式

有了字符组的概念后,一些常见的符号我们也就理解了。

因为它们都是系统自带的简写形式。

在这里插入图片描述

如果要匹配任意字符怎么办?可以使用 [\d\D]、[\w\W]、[\s\S] 和 [^] 中任何的一个。

### 1.3. 量词

量词也称重复。掌握 {m,n} 的准确含义后,只需要记住一些简写形式。

1.3.1. 简写形式

在这里插入图片描述

#### 1.3.2. 贪婪匹配与惰性匹配

var regex = /\d{2,5}/g;
var string = "123 1234 12345 123456";
console.log( string.match(regex) );
// => ["123", "1234", "12345", "12345"]

其中正则 /\d{2,5}/,表示数字连续出现 2 到 5 次。

会匹配 2 位、3 位、4 位、5 位连续数字。 但是其是贪婪的,它会尽可能多的匹配。

你能给我 6 个,我就要 5 个。你能给我 3 个,我就要 3 个。 反正只要在能力范围内,越多越好;

而惰性匹配,就是尽可能少的匹配:

var regex = /\d{2,5}?/g;
var string = "123 1234 12345 123456";
console.log( string.match(regex) );
// => ["12", "12", "34", "12", "34", "12", "34", "56"]

其中 /\d{2,5}?/ 表示,虽然 2 到 5 次都行,当 2 个就够的时候,就不再往下尝试了。

通过在量词后面加个问号就能实现惰性匹配,因此所有惰性匹配情形如下:

在这里插入图片描述

TIP 对惰性匹配的记忆方式是:量词后面加个问号,问一问你知足了吗,你很贪婪吗?

1.4. 多选分支

一个模式可以实现横向和纵向模糊匹配。

而多选分支可以支持多个子模式任选其一。

具体形式如下:(p1|p2|p3),其中 p1、p2 和 p3 是子模式,用 |(管道符)分隔,表示其中任何之一。

例如要匹配字符串 “good” 和 “nice” 可以使用 /good|nice/

var regex = /good|nice/g;
var string = "good idea, nice try.";
console.log( string.match(regex) );
// => ["good", "nice"]

但有个事实我们应该注意,比如我用 /good|goodbye/,去匹配 “goodbye” 字符串时,结果是 “good”:

var regex = /good|goodbye/g; 
var string = "goodbye"; 
console.log( string.match(regex) ); 
// => ["good"] 

而把正则改成 /goodbye|good/,结果是:

var regex = /goodbye|good/g; 
var string = "goodbye"; 
console.log( string.match(regex) ); 
// => ["goodbye"] 

也就是说,分支结构也是惰性的,即当前面的匹配上了,后面的就不再尝试了。

1.5. 案例

匹配字符,无非就是字符组、量词和分支结构的组合使用罢了。

1.5.1 匹配16进制颜色值

要求匹配: #ffbbad #Fc01DF #FFF #ffE

let reg = /#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})/g;

为什么{6}在前{3} 在后? 分支|是惰性匹配, 若是{3}在前, 则一直不会匹配到{6}的情况

1.5.2 匹配时间

要求匹配

23:59
02:07
let reg = /^([01]\d|2[0-3]):[0-5]\d$/;
// 如果也要求匹配 "7:9",也就是说时分前面的 "0" 可以省略。
let reg = /^(0?\d|1\d|2[0-3]):(0?|[1-5])\d$/;

正则中使用了 ^ 和 $,分别表示字符串开头和结尾

1.5.3 匹配日期

比如 yyyy-mm-dd 格式为例。 要求匹配: 2017-06-10

let reg =/^\d{4}-(0\d|1[012])-(0[1-9]|[12]\d|3[01])$/;
1.5.4. window 操作系统文件路径

要求匹配:

F:\study\javascript\regex\regular expression.pdf 
F:\study\javascript\regex\ 
F:\study\javascript
F:\

var regex = /^[a-zA-Z]:\\([^\\:*<>|"?\r\n/]+\\)*([^\\:*<>|"?\r\n/]+)?$/;
1.5.5. 匹配 id

要求从

<div id="container" class="main"></div>

提取出 id=“container”。

let reg = /id=".*"/;

var string = '<div id="container" class="main"></div>';
console.log(string.match(reg)[0]);
// => id="container" class="main"

因为 . 是通配符,本身就匹配双引号的,而量词 * 又是贪婪的,当遇到 container 后面双引号时,是不会 停下来,会继续匹配,直到遇到最后一个双引号为止。

解决之道,可以使用惰性匹配:

var regex = /id=".*?"/
var string = '<div id="container" class="main"></div>';
console.log(string.match(regex)[0]);
// => id="container"

第二章 正则表达式位置匹配攻略

2.1. 什么是位置呢?

位置(锚)是相邻字符之间的位置。比如,下图中箭头所指的地方:

在这里插入图片描述

2.2. 如何匹配位置呢?

在 ES5 中,共有 6 个锚:

^、$、\b、\B、(?=p)、(?!p)

2.2.1. ^ 和 $

^(脱字符)匹配开头,在多行匹配中匹配行开头。

$(美元符号)匹配结尾,在多行匹配中匹配行结尾。

比如我们把字符串的开头和结尾用 “#” 替换(位置可以替换成字符的!)

var result = "hello".replace(/^|$/g, '#'); 
console.log(result); 
// => "#hello#"

多行匹配模式(即有修饰符m)时,二者是行的概念,这一点需要我们注意:

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

\b 是单词边界,具体就是 \w 与 \W 之间的位置,也包括 \w 与 ^ 之间的位置,和 \w 与 $ 之间的位置。

var result = "[JS] Lesson_01.mp4".replace(/\b/g, '#');
console.log(result);
// => "[#JS#] #Lesson_01#.#mp4#"

\B 就是 \b 的反面的意思,非单词边界。例如在字符串中所有位置中,扣掉 \b,剩下的都是 \B 的。

具体说来就是 \w 与 \w、 \W 与 \W、^ 与 \W,\W 与 $ 之间的位置。

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"
2.2.3. (?=p) 和 (?!p)

(?=p),其中 p 是一个子模式,即 p 前面的位置,或者说,该位置后面的字符要匹配 p。

比如 (?=l),表示 “l” 字符前面的位置,例如:

var result = "hello".replace(/(?=l)/g, '#'); 
console.log(result); 
// => "he#l#lo" 

而 (?!p) 就是 (?=p) 的反面意思,该位置后面的字符不能匹配 p。 比如:

var result = "hello".replace(/(?!l)/g, '#'); 
console.log(result); 
// => "#h#ell#o#"

#### 2.3. 位置的特性

对于位置的理解,我们可以理解成空字符 “”。

比如 “hello” 字符串等价于如下的形式:

"hello" == "" + "h" + "" + "e" + "" + "l" + "" + "l" + "" + "o" + "";

2.4. 相关案例

2.4.1. 不匹配任何东西的正则

让你写个正则不匹配任何东西

easy,/.^/

因为此正则要求只有一个字符,但该字符后面是开头,而这样的字符串是不存在的。

2.4.2 数字的千位分隔符表示法

比如把 “12345678”,变成 “12,345,678”。 可见是需要把相应的位置替换成 “,”。 思路是什么呢?

3,345
123,445
12,345,678

// 1. 弄出最后一个逗号
let reg = /(?=\d{3}$)/;
let str= "1234";
str.replace(reg, ',')
// => "1,234"

// 2. 弄出所有的逗号
var result = "12345678".replace(/(?=(\d{3})+$)/g, ',')
console.log(result);
// => "12,345,678"

// 3. 验证问题
var result = "123456789".replace(/(?=(\d{3})+$)/g, ',')
console.log(result);
// => ",123,456,789"
// 怎么解决呢?我们要求匹配的到这个位置不能是开头。 ,(?!^),

// 4. 最终答案
var regex = /(?!^)(?=(\d{3})+$)/g;

// 5 如果要把 "12345678 123456789" 替换成 "12,345,678 123,456,789"。
// 此时我们需要修改正则,把里面的开头 ^ 和结尾 $,修改成 \b:
var string = "12345678 123456789",
regex = /(?!\b)(?=(\d{3})+\b)/g;
var result = string.replace(regex, ',')
console.log(result);
// => "12,345,678 123,456,789"

其中 (?!\b) 怎么理解呢?
要求当前是一个位置,但不是 \b 前面的位置,其实 (?!\b) 说的就是 \B。
因此最终正则变成了:/\B(?=(\d{3})+\b)/g。
2.4.3. 验证密码问题

密码长度 6-12 位,由数字、小写字符和大写字母组成,但必须至少包括 2 种字符。

// 先不考虑“但必须至少包括 2 种字符”这一条件 可得
let reg = /^[0-9a-zA-Z]{6,12}$/;
// 假设,要求的必须包含数字,怎么办?此时我们可以使用 (?=.*[0-9]) 来做。
let regex = /(?=.*[0-9])^[0-9A-Za-z]{6,12}$/;
// 再次假设, 同时包含数字和小写字母,可以用 (?=.*[0-9])(?=.*[a-z]) 来做
let regex = /(?=.*[0-9])(?=.*[a-z])^[0-9A-Za-z]{6,12}$/;
// 最终答案
let regex = /((?=.*[0-9])(?=.*[a-z])|(?=.*[0-9])(?=.*[A-Z])|(?=.*[a-z])(?=.*[AZ]))^[0-9A-Za-z]{6,12}$/;

另外一种解法

“至少包含两种字符”的意思就是说,不能全部都是数字,也不能全部都是小写字母,也不能全部都是大写 字母。

// 那么要求“不能全部都是数字”,怎么做呢? `(?!p)` 出马!
var regex = /(?!^[0-9]{6,12}$)^[0-9A-Za-z]{6,12}$/;
// 最终答案
var regex = /(?!^[0-9]{6,12}$)(?!^[a-z]{6,12}$)(?!^[A-Z]{6,12}$)^[0-9A-Za-z]{6,12}$/;

第三章 正则表达式括号的作用

括号提供了分组,便于我们引用它。

引用某个分组,会有两种情形:在 JavaScript 里引用它,在正则表达式里引用它。

3.1. 分组和分支结构

这二者是括号最直觉的作用,也是最原始的功能,强调括号内的正则是一个整体,即提供子表达式。

3.1.1. 分组

我们知道 /a+/ 匹配连续出现的 “a”,而要匹配连续出现的 “ab” 时,需要使用 /(ab)+/。

其中括号是提供分组功能,使量词 + 作用于 “ab” 这个整体.

var regex = /(ab)+/g;
var string = "ababa abbb ababab";
console.log( string.match(regex) );
// => ["abab", "ab", "ababab"]
3.1.2. 分支结构

而在多选分支结构 (p1|p2) 中,此处括号的作用也是不言而喻的,提供了分支表达式的所有可能。

比如,要匹配如下的字符串:

I love JavaScript

I love Regular Expression

var regex = /^I love (JavaScript|Regular Expression)$/;
console.log( regex.test("I love JavaScript") );
console.log( regex.test("I love Regular Expression") );
// => true
// => true

3.2. 分组引用

这是括号一个重要的作用,有了它,我们就可以进行数据提取,以及更强大的替换操作。

以日期为例。假设格式是 yyyy-mm-dd 的,我们可以先写一个简单的正则:

var regex = /\d{4}-\d{2}-\d{2}/;

在这里插入图片描述

然后再修改成括号版的:

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

在这里插入图片描述

对比这两个可视化图片,我们发现,与前者相比,后者多了分组编号,如 Group #1。

其实正则引擎也是这么做的,在匹配过程中,给每一个分组都开辟一个空间,用来存储每一个分组匹配到的 数据。

既然分组可以捕获数据,那么我们就可以使用它们。

3.2.1. 提取数据

比如提取出年、月、日,可以这么做:

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"]

var regex = /(\d{4})-(\d{2})-(\d{2})/g; 
var string = "2017-06-12"; 
console.log( string.match(regex) ); 
// ['2017-06-12']

match 返回的一个数组,第一个元素是整体匹配结果,然后是各个分组(括号里)匹配的 内容,然后是匹配下标,最后是输入的文本。另外,正则表达式是否有修饰符 g,match 返回的数组格式是不一样的。

另外也可以使用正则实例对象的 exec 方法:

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

同时,也可以使用构造函数的全局属性 $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"
3.2.2. 替换

比如,想把 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"

其中 replace 中的,第二个参数里用 $1、$2、$3 指代相应的分组。等价于如下的形式:

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"

也等价于:

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"

3.3. 反向引用

除了使用相应 API 来引用分组,也可以在正则本身里引用分组。但只能引用之前出现的分组,即反向引用.

还是以日期为例。 比如要写一个正则支持匹配如下三种格式:

2016-06-12

2016/06/12

2016.06.12

最先可能想到的正则是:

var regex = /\d{4}(-|\/|\.)\d{2}(-|\/|\.)\d{2}/;
var string1 = "2017-06-12";
var string2 = "2017/06/12";
var string3 = "2017.06.12";
var string4 = "2016-06/12";
console.log( regex.test(string1) ); // true
console.log( regex.test(string2) ); // true
console.log( regex.test(string3) ); // true
console.log( regex.test(string4) ); // true

其中 / 和 . 需要转义。虽然匹配了要求的情况,但也匹配 “2016-06/12” 这样的数据。

假设我们想要求分割符前后一致怎么办?此时需要使用反向引用:

var regex = /\d{4}(-|\/|\.)\d{2}\1\d{2}/;
var string1 = "2017-06-12";
var string2 = "2017/06/12";
var string3 = "2017.06.12";
var string4 = "2016-06/12";
console.log( regex.test(string1) ); // true
console.log( regex.test(string2) ); // true
console.log( regex.test(string3) ); // true
console.log( regex.test(string4) ); // false

注意里面的 \1,表示的引用之前的那个分组 (-|/|.)。不管它匹配到什么(比如 -),\1 都匹配那个同 样的具体某个字符。

我们知道了 \1 的含义后,那么 \2 和 \3 的概念也就理解了,即分别指代第二个和第三个分组。

看到这里,此时,恐怕你会有几个问题。

3.3.1. 括号嵌套怎么办?

以左括号(开括号)为准。比如:

var regex = /^((\d)(\d(\d)))\1\2\3\4$/;
var string = "1231231233";
console.log( regex.test(string) ); // true
console.log( RegExp.$1 ); // 123
console.log( RegExp.$2 ); // 1
console.log( RegExp.$3 ); // 23
console.log( RegExp.$4 ); // 3

我们可以看看这个正则匹配模式:

  1. 第一个字符是数字,比如说 “1”,
  2. 第二个字符是数字,比如说 “2”,
  3. 第三个字符是数字,比如说 “3”,
  4. 接下来的是 \1,是第一个分组内容,那么看第一个开括号对应的分组是什么,是 “123”,
  5. 接下来的是 \2,找到第2个开括号,对应的分组,匹配的内容是 “1”,
  6. 接下来的是 \3,找到第3个开括号,对应的分组,匹配的内容是 “23”,
  7. 最后的是 \4,找到第3个开括号,对应的分组,匹配的内容是 “3”。
3.3.2. \10 表示什么呢?

另外一个疑问可能是,即 \10 是表示第 10 个分组,还是 \1 和 0 呢?

答案是前者,虽然一个正则里出现 \10 比较罕见。

测试如下:

var regex = /(1)(2)(3)(4)(5)(6)(7)(8)(9)(#) \10+/; 
var string = "123456789# ######";
console.log( regex.test(string) ); 
// => true

TIP 如果真要匹配 \1 和 0 的话,请使用 (?:\1)0 或者 \1(?:0)。

3.3.3. 引用不存在的分组会怎样?

因为反向引用,是引用前面的分组,但我们在正则里引用了不存在的分组时,此时正则不会报错,只是匹配 反向引用的字符本身。例如 \2,就匹配 “\2”。注意 “\2” 表示对 “2” 进行了转义。

var regex = /\1\2\3\4\5\6\7\8\9/;
console.log( regex.test("\1\2\3\4\5\6\7\8\9") );
console.log( "\1\2\3\4\5\6\7\8\9".split("") );

#### 3.3.4. 分组后面有量词会怎样?

分组后面有量词的话,分组最终捕获到的数据是最后一次的匹配。比如如下的测试案例:

var regex = /(\d)+/;
var string = "12345";
console.log( string.match(regex) );
// => ["12345", "5", index: 0, input: "12345"]

从上面看出,分组 (\d) 捕获的数据是 “5”。

同理对于反向引用,也是这样的。测试如下:

var regex = /(\d)+ \1/;
console.log( regex.test("12345 1") );
// => false
console.log( regex.test("12345 5") );
// => true

### 3.4. 非捕获括号

之前文中出现的括号,都会捕获它们匹配到的数据,以便后续引用,因此也称它们是捕获型分组和捕获型分支。

如果只想要括号最原始的功能,但不会引用它,即,既不在 API 里引用,也不在正则里反向引用。 此时可以使用

非捕获括号 (?:p) 和 (?:p1|p2|p3),例如本章第一个例子可以修改为:

var regex = /(?:ab)+/g;
var string = "ababa abbb ababab";
console.log( string.match(regex) );
// => ["abab", "ab", "ababab"]

同理,第二例子可以修改为:

var regex = /^I love (?:JavaScript|Regular Expression)$/;
console.log( regex.test("I love JavaScript") );
console.log( regex.test("I love Regular Expression") );
// => true
// => true

3.5. 相关案例

3.5.1. 字符串 trim 方法模拟

trim方法是去掉字符串的开头和结尾的空白符。

let reg =/^\s+|\s+$/g;
let str = " 123 ";
console.log(str);
let newStr = str.replace(reg, '');
console.log(newStr);
// => 
3.5.2. 将每个单词的首字母转换为大写
function titleize (str) {
  return str.toLowerCase().replace(/(?:^|\s)\w/g, function (c) {
  return c.toUpperCase();
  });
}
console.log( titleize('my name is epeli') );
// => "My Name Is Epeli"
3.5.3. 驼峰化
function camelize (str) {
  return str.replace(/[-_\s]+(.)?/g, function (match, c) {
    console.log('match', match, 'c', c);
    return c ? c.toUpperCase() : '';
  });
}
console.log( camelize('-moz-transform') );
// match -m c m
// match -t c t
// => "MozTransform"

其中分组 (.) 表示首字母。单词的界定是,前面的字符可以是多个连字符、下划线以及空白符。

正则后面 的 ? 的目的,是为了应对 str 尾部的字符可能不是单词字符,比如 str 是 '-moz-transform '。

3.5.4. 中划线化
function dasherize (str) {
  return str.replace(/([A-Z])/g, '-$1').replace(/[-_\s]+/g, '-').toLowerCase();
}
console.log( dasherize('MozTransform') );
// => "-moz-transform"
3.5.6. 匹配成对标签

要求匹配:

<title>regular expression</title>
<p>laoyao bye bye</p>

不匹配:

<title>wrong!</p>
// 匹配一个开标签,可以使用正则 <[^>]+>,
// 匹配一个闭标签,可以使用 <\/[^>]+>,
// 但是要求匹配成对标签,那就需要使用反向引用,如:

var regex = /<([^>]+)>[\d\D]*<\/\1>/;
var string1 = "<title>regular expression</title>";
var string2 = "<p>laoyao bye bye</p>";
var string3 = "<title>wrong!</p>";
console.log( regex.test(string1) ); // true
console.log( regex.test(string2) ); // true
console.log( regex.test(string3) ); // false

文章参考原地址: https://zhuanlan.zhihu.com/p/29707385

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值