Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

正则表达式入门 #14

Open
iwalking11 opened this issue Dec 20, 2018 · 4 comments
Open

正则表达式入门 #14

iwalking11 opened this issue Dec 20, 2018 · 4 comments

Comments

@iwalking11
Copy link
Owner

image

@iwalking11
Copy link
Owner Author

iwalking11 commented Dec 21, 2018

转义字符

正则表达式中,需要反斜杠转义的,一共有12个字符:^、.、[、$、(、)、|、*、+、?、{和\。需要特别注意的是,如果使用RegExp方法生成正则对象,转义需要使用两个斜杠,因为字符串内部会先转义一次。

(new RegExp('1\+1')).test('1+1')
// false

(new RegExp('1\\+1')).test('1+1')
// true

RegExp作为构造函数,参数是一个字符串。但是,在字符串内部,反斜杠也是转义字符,所以它会先被反斜杠转义一次,然后再被正则表达式转义一次,因此需要两个反斜杠转义。

使用正则表达式节

正则表达式可以被用于RegExp的exec和test方法以及 String的match、replace、search和split方法。这些方法在JavaScript 手册中有详细的解释。
RegExp 对象

@iwalking11
Copy link
Owner Author

忘记了,就看看这篇文章
JS正则表达式完整教程(略长)

@iwalking11
Copy link
Owner Author

操作符的优先级从上至下,由高到低:

1.转义符 \
2.括号和方括号 (...)、(?:...)、(?=...)、(?!...)、[...]
3.量词限定符 {m}、{m,n}、{m,}、?、*、+
4.位置和序列 ^ 、$、 \元字符、 一般字符
5. 管道符(竖杠)|

image

@iwalking11
Copy link
Owner Author

引言

  • 第1-2章介绍了正则如何匹配字符和位置。
    正则要么是匹配字符、要么匹配位置。

  • 第3章介绍了正则中括号的作用。
    在正则中可以使用括号捕获数据,要么在API中进行分组引用,要么在正则里进行反向引用。

  • 第4章介绍了正则匹配的回溯原理。
    要想提高正则匹配的效率,需要尽可能的减少匹配中的回溯次数。

  • 第5章介绍了正则中操作符的优先级(尤其重要)
    明白了一些操作符优先级顺序,这样才能看懂一些复杂的正则表达式,正确拆分正则。

  • 第6章介绍了js中关于正则的一些API。
    这是真正用到正则的一些场景

1 匹配字符

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

1.1 模糊匹配

横向模糊匹配

(使用量词,一个正则可以匹配的字符串长度不固定)

纵向模糊匹配

(使用字符组,具体到某一个字符可以有多种匹配可能)

1.2 字符组 [abc]

  • 1.2.1 范围表示法

[a-zA-Z1-9] (- 连字符)

  • 1.2.2 排除字符组[^abc]

表示是一个除"a"、"b"、"c"之外的任意一个字符。字符组的第一位放^(脱字符),表示求反的概念。

  • 1.2.3 字符组的一些简写形式
\d就是[0-9]。表示是一位数字。记忆方式:其英文是digit(数字)。

\D就是[^0-9]。表示除数字外的任意字符。

\w就是[0-9a-zA-Z_]。表示数字、大小写字母和下划线。记忆方式:w是word的简写,也称单字符    。

\W是[^0-9a-zA-Z_]。非单词字符。

\s是[ \t\v\n\r\f]。表示空白符,包括空格、水平制表符、垂直制表符、换行符、回车符、页符    。
记忆方式:s是space character的首字母。

\S是[^ \t\v\n\r\f]。 非空白符。.就是[^\n\r\u2028\u2029]。通配符,表示几乎任意字符。
换行符、回车符、行分隔符和段分隔符除外。
记忆方式:想想省略号...中的每个点,都可以理解成占位符,表示任何类似的东西。

1.3 量词

? 0次或者1次
+ 1次或者多次
* 任意次数
{m,n} m-n次
{m} m次
贪婪匹配与非贪婪匹配(惰性匹配)

以上量词默认情况下尽可能匹配更多的字符,这被称为贪婪模式。
非贪婪模式也就是惰性匹配,只需要在量词后面加个问号?就可以实现
因此惰性匹配情况如下:

?? 表示某个模式出现1次或多次,匹配时采用非贪婪模式。
+? 表示某个模式出现0次或多次,匹配时采用非贪婪模式。
*? 表格某个模式出现0次或1次,匹配时采用非贪婪模式。
{m,n}? 表格某个模式出现m次至n次,匹配时采用非贪婪模式。

注意:
??和*?并不能简单的都理解为出现0次 需要看具体使用的场景,比如:

'a11a'.match(/a(\d*?)a/);
//  ["a11a", "11", index: 0, input: "a11a", groups: undefined]
'a11a'.match(/a(\d??)a/) ;
// null

1.4 多选分支 | (默认惰性匹配)

一个模式支持横向模糊匹配和纵向模糊匹配,而多选分支支持多个子模式任选其一。
(p1|p2|p3),其中p1,p2,p3代表子模式,用管道符|分隔,表示其中任何之一。

1.5 案例分析

1.5.1 匹配16进制颜色值

要求匹配:

#ffbbad
#Fc01DF
#FFF
#ffE

/^#(?:[1-9a-fA-F]{3}|[1-9a-fA-F]{6}$/

1.5.2 匹配时间

以24小时制为例。
要求匹配:

23:59
02:07

let regex = /^(?:[01]?\d|2[0-3]):[0-5]?\d$/
console.log( regex.test("23:59") ); //true
console.log( regex.test("02:07") ); //true
console.log( regex.test("7:9") ); //true

1.5.3 匹配id

要求从

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

提取出id="container"。
/id="[^"]*"/

2 位置匹配

  • 什么是位置?
相邻字符之间的位置
  • 如何匹配位置?
ES5中,共有6个锚字符:

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

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

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

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

\B就是\b的反面的意思,非单词边界。例如在字符串中所有位置中,扣掉\b,剩下的都是\B。
具体说来就是\w与\w、\W与\W、^与\W,\W与$之间的位置。

(?=p),正向先行断言,其中p是一个子模式,即p前面的位置。比如(?=l),表示'l'字符前面位置

(?!p),负向先行断言。就是(?=p)的反面意思
  • 位置的特性
    对于位置的理解,我们可以理解成空字符""。
var result = /(?=he)^^he(?=\w)llo$\b\b$/.test("hello");
console.log(result); 
// => true

也就是说字符之间的位置,可以写成多个。

把位置理解空字符,是对位置非常有效的理解方式。

  • 几个应用实例分析
  1. 不匹配任何东西的正则
    /.^/
  1. 数字的千位分隔符表示法
var string1 = "12345678",
string2 = "123456789";
reg = /(?!^)(?=(\d{3})+$)/g;

var result = string1.replace(reg, ',')
console.log(result); 
// => "12,345,678"

result = string2.replace(reg, ',');
console.log(result); 
// => "123,456,789"

如果要把"12345678 123456789"替换成"12,345,678 123,456,789"
最终正则变成了:/\B(?=(\d{3})+\b)/g。
3. 验证密码问题
密码长度6-12位,由数字、小写字符和大写字母组成,但必须至少包括2种字符。

我们可以把原题变成下列几种情况之一:

同时包含数字和小写字母
同时包含数字和大写字母
同时包含小写字母和大写字母

var reg = /((?=.*[0-9])(?=.*[a-z])|(?=.*[0-9])(?=.*[A-Z])|(?=.*[a-z])(?=.*[A-Z]))^[0-9A-Za-z]{6,12}$/;
console.log( reg.test("1234567") ); // false 全是数字
console.log( reg.test("abcdef") ); // false 全是小写字母
console.log( reg.test("ABCDEFGH") ); // false 全是大写字母
console.log( reg.test("ab23C") ); // false 不足6位
console.log( reg.test("ABCDEF234") ); // true 大写字母和数字
console.log( reg.test("abcdEF234") ); // true 三者都有

另一种解法

var reg = /(?!^[0-9]{6,12}$)(?!^[a-z]{6,12}$)(?!^[A-Z]{6,12}$)^[0-9A-Za-z]{6,12}$/;
console.log( reg.test("1234567") ); // false 全是数字
console.log( reg.test("abcdef") ); // false 全是小写字母
console.log( reg.test("ABCDEFGH") ); // false 全是大写字母
console.log( reg.test("ab23C") ); // false 不足6位
console.log( reg.test("ABCDEF234") ); // true 大写字母和数字
console.log( reg.test("abcdEF234") ); // true 三者都有

3 正则表达式括号的作用

  • 分组和分支结构
/(ab)+/  (p1|p2)
  • 捕获分组
$1、$2、$3
  • 反向引用
\1、\2、\3
  • 非捕获分组
(?:p)
  • 案例
  1. 字符串trim方法模拟
str.replace(/^\s+|\s+$/g, '')

str.replace(/^\s*(.*?)\s*$/g, "$1");
该方法会回溯,效率不高
  1. 将每个单词的首字母转换为大写
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"
  1. 驼峰化
function camelize(str) {
	return str.replace(/[-_\s]+(.)?/g, function(match, c) {
		return c ? c.toUpperCase() : '';
	});
}
console.log( camelize('-moz-transform') ); 
// => "MozTransform"
  1. 中划线化
function dasherize(str) {
	return str.replace(/([A-Z])/g, '-$1').replace(/[-_\s]+/g, '-').toLowerCase();
}
console.log( dasherize('MozTransform') ); 
// => "-moz-transform"

4 正则表达式回溯法原理

贪婪量词、 惰性量词、分支结构都有可能产生回溯。

简单总结就是,正因为有多种可能,所以要一个一个试。直到,要么到某一步时,整体匹配成功了;要么最后都试完后,发现整体匹配不成功。

  • 贪婪量词“试”的策略是:买衣服砍价。价钱太高了,便宜点,不行,再便宜点。
  • 惰性量词“试”的策略是:卖东西加价。给少了,再多给点行不,还有点少啊,再给点。
  • 分支结构“试”的策略是:货比三家。这家不行,换一家吧,还不行,再换。

既然有回溯的过程,那么匹配效率肯定低一些。
相对谁呢?相对那些DFA引擎。而JS的正则引擎是NFA,NFA是“非确定型有限自动机”的简写。
大部分语言中的正则都是NFA,为啥它这么流行呢?答:你别看我匹配慢,但是我编译快啊,而且我还有趣哦。

5 正则表达式的拆分

  • JS正则表达式中,都有哪些结构呢?
字面量,匹配一个具体字符,包括不用转义的和需要转义的。
比如a匹配字符"a",又比如\n匹配换行符,又比如\.匹配小数点。

字符组,匹配一个字符,可以是多种可能之一,比如[0-9],表示匹配一个数字。也有\d的简写形式。
另外还有反义字符组,表示可以是除了特定字符之外任何一个字符,比如[^0-9],表示一个非数字字符,也有\D的简写形式。

量词,表示一个字符连续出现,比如a{1,3}表示“a”字符连续出现3次。
另外还有常见的简写形式,比如a+表示“a”字符连续出现至少一次。

锚点,匹配一个位置,而不是字符。比如^匹配字符串的开头,又比如\b匹配单词边界,又比如(?=\d)表示数字前面的位置。

分组,用括号表示一个整体,比如(ab)+,表示"ab"两个字符连续出现多次,也可以使用非捕获分组(?:ab)+。

分支,多个子表达式多选一,比如abc|bcd,表达式匹配"abc"或者"bcd"字符子串。反向引用,比如\2,表示引用第2个分组。

其中涉及到的操作符有:

1.转义符 \
2.括号和方括号 (...)、(?:...)、(?=...)、(?!...)、[...]
3.量词限定符 {m}、{m,n}、{m,}、?、*、+
4.位置和序列 ^ 、$、 \元字符、 一般字符
5.管道符(竖杠)|

上面操作符的优先级从上至下,由高到低。

  • 案例
  1. 身份证
/^(\d{15}|\d{17}[\dxX])$/
  1. IPV4地址
/^((0{0,2}\d|0?\d{2}|1\d{2}|2[0-4]\d|25[0-5])\.){3}(0{0,2}\d|0?\d{2}|1\d{2}|2[0-4]\d|25[0-5])$/

6 正则表达式API

String的实例四个:
String.prototype.search
String.prototype.match
String.prototype.replace
String.prototype.split

RegExp的实例两个:
RegExp.prototype.test
ResExp.prototype.exec

参考资料

JS正则表达式完整教程(略长) 目前看过的最好的一个正则的教程,本篇也是对该教程的总结
RegExp 对象
阮一峰的js教程
正则表达式30分钟入门教程 看的第一篇完整正则教程,虽说是十多年前的教程,但是写的很详细
我的有道云正则笔记

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant