平时在使用一些正则都是网上拷贝下来的,知其用而不知道所以然,现在发现得好好学习一下正则表达式了。

在学习正则之前,我们得先了解一些正则的元字符

RegExp对象

声明一个正则有两种写法

let re = /[\d]+/g;
let re1 = new RegExp('[\\d]+', 'g');
// es6使用构造函数创建正则,可以执行修饰符
let re2 = new RegExp(/\w+/g, 'i');  // /\w+/i

两者的区别就是,new RegExp的第一个参数字符串的转义字符要两个斜杠,第二个参数是修饰符

  • i 执行对大小写不敏感的匹配
  • g 执行全局匹配
  • m 执行多行匹配
  • y 执行全局匹配,后一次匹配从上一次匹配成功的下一个位置开始(es6新增的修饰符)
var str = 'aaa_aa_aa';
var reg = /a+/g;
reg.exec(str);	// [aaa]
reg.exec(str);	// [aa]
reg = /a+/y;
reg.exec(str);	// [aaa]
reg.exec(str);	// []

正则方法

正则对象有三个方法,分别为test()exec()compile()

  • test()检索字符串是否有对应的值,返回true或false

比如检查input输入是否合法,不需要获取匹配值,可以使用test()方法

let reg = /[\w]+/;        // 只能是数字或者字符串和下划线
reg.test('肯德基');    // false
reg.test('aa_123');    // true
  • exec()检索字符串的指定值,返回null或者数组

比如我们要获取匹配的指定值,可以使用exec

/abc/.exec('a');    // null
/abc/.exec('dcba abc qwer abc');    //    匹配第一个就停止,返回 ['abc']    

如果需要匹配子项的话

/(ab)c/.exec('dcba abc qwer abc');    // 返回一个数组,第二个参数是括号的内容 ['abc', 'ab']

上面的例子匹配到第一个就会停止了,如果要继续匹配,可以使用while循环,正则需要加上g修饰符,如果没有会死循环

let re = /abc/g, ret;
while (ret = re.exec('dcba abc qwer abc')) {
    console.log(ret)
}
// 分别打印出以下结果,由于使用了全局匹配,会返回额外的indexinput结果
// ["abc", index: 5, input: "dcba abc qwer abc"]
// ["abc", index: 14, input: "dcba abc qwer abc"]
  • compile()可以执行,改变和重新编译正则表达式

该方法可以复用一个对象,在代码执行的过程中重新改变匹配方式,用的地方相对较少

正则应用

这里举一个例子,例如需要校验用户名是否符合需求

需求:用户名以字母开头+数字/字母/下划线,长度不小于6不大于12

分析:

  1. 以字母开头的正则 ^[a-zA-Z]
  2. 数字/字母/下划线的正则 \w ==> [a-zA-Z0-9_]
  3. 由于首字母占用一位,剩下5-11个长度,{5,11}$

通过上面的分析,可以合并为一个表达式:/^[a-zA-Z]\w{5,11}$/

贪婪模式

贪婪模式会匹配尽可能多的字符,比如

let ret = /a+/.exec('aaaaaa')        // ["aaaaaa", index: 0, input: "aaaaaa", groups: undefined]

上面的正则会尽量去匹配多个字符,结果返回的是 aaaaaa

非贪婪模式

非费贪婪模式会尽量少的匹配,只需要在修饰符后面加上?即可

// 很懒,只匹配到一个就停止了
let ret = /a+?/.exec('aaaaaa')     // ["a", index: 0, input: "aaaaaa", groups: undefined]

非贪婪模式的使用场景,比如有这样的html字符串

let html = "<div>div1</div><div>div2</div>"

我们想取<div>div1</div>字符串,获取你想这样写

let ret = /<div>.*<\/div>/.exec(html)        // "<div>div1</div><div>div2</div>"

结果返回的是整个html字符串,这时候非贪婪模式就可以派上用场了

let ret = /<div>.*?<\/div>/.exec(html)    // "<div>div1</div>"

再举个例子,比如要截取一个图片"a.jpg.jpg.png"字符串的"a.jpg"

let url = "a.jpg.jpg.png";
let ret = /.*?\.jpg/.exec(url)    // a.jpg

字符串和正则

字符串有4个方法可以使用正则,分别是match()replace()search()split()

String.replace

String.replace() 可以使用正则来替换字符串,例如

let str = "abc you abc me";
str.replace(/abc/g, 'fuck');        // fuck your fuck me

第二个参数还可以是回调函数,于是上面的写法还可以这样写

let str = "abc you abc me";
str.replace(/abc/g, match => {
    // match是正则匹配到的字符串
    return 'fuck';
})

String.replace和while循环

例如我们想要获取url的所有字符串,我们可能会这么写

let url = "ttp://a.com?a=1&b=abc&q=%e5%b0%bc%e7%8e%9b&名字=xx";
let reg = /[\?&]([^&#]+)=([^&#]*)/g, params = {}, ret;
// 使用while
while (ret = reg.exec(url)) {
    params[ret[1]] = ret[2];
}
console.log(params);
[^&#] 表示除了&和#外的所有字符

String.replace() 在全局模式下,第二个参数回调会一直调用知道搜索结束,所以可以这样写

let url = "ttp://a.com?a=1&b=abc&q=%e5%b0%bc%e7%8e%9b&名字=xx";
let reg = /[\?&]([^&#]+)=([^&#]*)/g, params = {}, ret;
// 使用replace
url.replace(reg, (match, sub1, sub2) => {
    params[sub1] = sub2;
})
console.log(params);

正向肯定预查

(?=pattern) 是正向肯定预查,例如:Windows(?=98|2000) 可以匹配Windows2000的Windows,不可以匹配Windows2008的Windows

利用这个规则可以实现匹配大小写和数字的正则

let reg = /^(?=.*[a-z])(?=.*[A-Z])(?=.*[\d]).{6,12}$/

看上去有点难懂,其实可以这样记,?=.*可以记为至少包含一个,比如

  • (?=.*[a-z])至少有一个小写字母
  • (?=.*foo) 至少有一个foo

(x)和(?:x)的区别

两个都是表示匹配x,(x)是记住匹配项,(?:x)不记住匹配项,其实就是匹配的结果要不要留给后续的操作使用

/(foo) (bar) \1 \2/.exec('foo bar foo bar')		// ["foo bar foo bar", "foo", "bar"],\1 \2 类似于字符串替换时候使用的 $1 $2 
/(foo) (bar) \1 \2/.exec('foo bar foo foo') 	// null
	
/(?:foo) (bar)/.exec('foo bar hello world')		// ['foo bar', 'bar']
/(foo) (bar)/.exec('foo bar hello world')		// ['foo bar', 'foo', 'bar']