const reg = /[a-z]\d+[a-z]/i;
const alphabet = '[a-z]';
const reg = new RegExp(`${alphabet}\\d+${alphabet}`, 'i');
const reg = new RegExp(`\d+`); // \d未二次转义
reg.test('1'); // false
reg.test('ddd'); // true
const reg = /[a-z]\d+[a-z]/i;
reg.test('a1a'); // true
reg.test('1a1'); // false
reg.test(Symbol('a1a')); // TypeError
const reg = /[a-z]\d+[a-z]/ig;
reg.source; // "[a-z]\d+[a-z]"
reg.flags; // "gi"
const reg = /[a-z]\d+[a-z]/i;
reg.exec('a1a'); // ["a1a", index: 0, input: "a1a", groups: undefined]
reg.exec('1a1'); // null
'a1a'.match(reg); // ["a1a", index: 0, input: "a1a", groups: undefined]
'1a1'.match(reg); // null
包含g修饰符时:
const reg = /(a)/g;
reg.exec('a1a'); // ["a", "a", index: 0, input: "a1a", groups: undefined]
'a1a'.match(reg); // ["a", "a"]
当正则表达式含有 g 修饰符时,RegExp.prototype.exec 每次只返回一个匹配结果,数据格式和不含 g 修饰符相同。
String.prototype.match 会返回所有的匹配结果,数据格式会变为字符串数组。
由于 String.prototype.match 返回的数据格式不固定,因此大多数情况都建议使用 RegExp.prototype.exec
4)RegExp.prototype.lastIndex
当前正则表达式最后一次匹配成功的结束位置(也就是下一次匹配的开始位置)
最主要是结合g修饰符使用。
const reg = /(a)/g;
const str = 'a1a';
reg.lastIndex; // 0 默认从0 开始
reg.exec('a1a'); // ["a", "a", index: 0, input: "a1a", groups: undefined]
reg.lastIndex; // 1
reg.exec('a1a'); // ["a", "a", index: 2, input: "a1a", groups: undefined]
reg.lastIndex; // 3
reg.exec('a1a'); // null
reg.lastIndex; // 0 手动重置
注意:lastIndex 不会自己重置,只有当上一次匹配失败才会重置为 0 ,因此,当你需要反复使用同一个正则表达式的时候,请在每次匹配新的字符串之前重置 lastIndex!
5)String.prototype.replace()、String.prototype.search()、String.prototype.split()
String.prototype.replace():替换,可以匹配正则表达式、字符串。
String.prototype.search():搜索匹配到的内容在字符串出现的位置。
'a1a'.replace(/a/, 'b'); // 'b1a'
'a1a'.replace(/a/g, 'b'); // 'b1b'
'a1a'.search(/a/); // 0
'a1a'.search(/a/g); // 0
'a1a'.split(/a/); // ["", "1", ""]
'a1a'.split(/a/g); // ["", "1", ""]
1)/[0-9]+/
[]
字符集,使用连字符 - 表示指定的字符范围,如果想要匹配连字符,需要挨着方括号放置,或进行转义;
0-9 表示匹配从 0 到 9 的数字字符,常用的还有 a-z 匹配小写字母,\u4e00-\u9fa5 匹配汉字等
如果只是匹配数字,还可以使用字符集缩写 \d
+
限定符,匹配一个或多个
缺点
不是全字符匹配,存在误判,如 /[0-9]+/.test(‘a1’) === true
2)/^\d+$/
3)/^[+-]?\d+(\.\d+)?$/
():圆括号内是一个子表达式,当圆括号不带任何修饰符时,表示同时创建一个捕获组
?:? 在正则中有多种含义,作为限定符时,表示匹配零到一个
\.
:可以匹配除换行符之外的任意字符,当结合 s 修饰符时,可以匹配包括换行符在内的任意字符
当匹配小数点字符时需要转义
缺点
不能匹配无整数部分的小数,如 .123
捕获组会带来额外的开销
4)/^[+-]?(?:\d*\.)?\d+$/
(?:)
:创建一个非捕获组1)完整的数值 token:https://drafts.csswg.org/css-syntax-3/#number-token-diagram
注意:这个 token 是 CSS 的 token,在 javascript 中,要多考虑一种情况:无小数部分
+'2.'; // 2
+'2.e1'; // 20
2)/^[+-]?(?:\d+\.?|\d*\.\d+)(?: e[+-]?\d+)?$/i
1)数值的解析
const reg = /[+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?(?=px|\s|$)/gi;
function execNumberList(str) {
reg.lastIndex = 0;
let exec = reg.exec(str);
const result = [];
while (exec) {
result.push(parseFloat(exec[0]));
exec = reg.exec(str);
}
return result;
}
console.log(execNumberList('1.0px .2px -3px +4e1px')); // [1, 0.2, -3, 40]
console.log(execNumberList('+1.0px -0.2px 3e-1px')); // [1, -0.2, 0.3]
console.log(execNumberList('1px 0')); // [1, 0] 0的时候无单位
console.log(execNumberList('-1e+1px')); // [-10]
(?=expression)
正向肯定环视 / 顺序肯定环视 / 先行断言
用于匹配符合条件的位置
类似的语法还有:
(?!expression) 正向否定环视 / 顺序否定环视 / 先行否定断言
(?<=expression) 反向肯定环视 / 逆序肯定环视 / 后行断言,es2018 新增
(?
const reg = /[+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?(?=px|\s|$)/gi;
2)数值转货币格式
function formatCurrency(str) {
// ……
}
console.log(formatCurrency('1')); // 1
console.log(formatCurrency('123')); // 123
console.log(formatCurrency('12345678')); // 12,345,678
const reg = /(\d)(?=(\d{3})+(,|$))/g;
function formatCurrency(str) {
return str.replace(reg, '$1,');
}
console.log(formatCurrency('1')); // 1
console.log(formatCurrency('123')); // 123
console.log(formatCurrency('12345678')); // 12,345,678
const reg = /(\d)(?=(?:\d{3})+(?:,|$))/g;
function formatCurrency(str) {
return str.replace(reg, '$1,');
}
const reg = /(\d)(?=(?:\d{3})+(?:,|$))/g;
function formatCurrency(str) {
return str.replace(reg, '$1,');
}
用于 replace 的字符串中,表示第 n 个捕获组,n 可以从 1 到 9
const reg = /\d(?=(?:\d{3})+(?:,|$))/g;
function formatCurrency(str) {
return str.replace(reg, '$&,');
}
在 es2018 以上的环境,还可以使用反向环视
const reg = /(?<=\d)(?=(?:\d{3})+(?:,|$))/g;
function formatCurrency(str) {
return str.replace(reg, ',');
}
其它注意事项
环视中的圆括号也会生成捕获组,所以都要采用 (? 的非捕获组形式
color: #rrggbb;
color: #rgb;
color: #rrggbbaa;
color: #rgba;
对应的正则写法
const hex = '[0-9a-fA-F]';
const reg = new RegExp(`^(?:#${hex}{6}|#${hex}{8}|#${hex}{3,4})$`);
其它注意事项
也可以使用 i 修饰符来匹配大小写,i 修饰符和 a-fA-F 要根据实际需求来做取舍
还记得前面的问题吗?
2)rgb/rgba 表示法
color: rgb(r, g, b);
color: rgb(r%, g%, b%);
color: rgba(r, g, b, a);
color: rgba(r%, g%, b%, a);
color: rgba(r, g, b, a%);
color: rgba(r%, g%, b%, a%);
对应的正则写法
const num = '[+-]?(?:\\d*\\.)?\\d+(?:e[+-]?\\d+)?';
const comma = '\\s*,\\s*';
const reg = new RegExp(`rgba?\\(\\s*${num}(%?)(?:${comma}${num}\\1){2}(?:${comma}${num}%?)?\\s*\\)`);
const num = '[+-]?(?:\\d*\\.)?\\d+(?:e[+-]?\\d+)?';
const comma = '\\s*,\\s*';
const reg = new RegExp(`rgba?\\(${num}(%?)(?:${comma}${num}\\1){2}(?:${comma}${num}%?)?\\)`);
\n
反向引用,表示引用第 n 个捕获组
由于 r/g/b 必须同时为数值或百分比,所以 %? 只需要捕获一次,用 \1 来引用
\s
字符集缩写,用于匹配空白
需要注意的点
按照规范,rgb(r,g,b,a) 和 rgba(r,g,b) 也是合法的
r/g/b 的值应该是 0~255 的整数,但是溢出或小数并不会报错
当捕获组内的内容是可选的时候,一定要把问号写在捕获组内
如果可选内容的圆括号不可省略,如(a|b|c)?,应该多嵌套一层:((?:a|b|c)?)
3)其它
/* hsl & hsla */
color: hsl(h, s%, l%);
color: hsla(h, s%, l%, a);
color: hsla(h, s%, l%, a%);
/* keywords */
color: red;
color: blue;
/* …… */
更多的颜色表示方法:
https://www.w3.org/TR/css-color/
function shortenColor(str) {
// ……
}
console.log(shortenColor('#336600')); // '#360'
console.log(shortenColor('#19b955')); // '#19b955'
console.log(shortenColor('#33660000')); // '#3600'
const hex = '[0-9a-z]';
const hexReg = new RegExp(`^#(? ${hex})\\k(? ${hex})\\k(? ${hex})\\k(?${hex}?)\\k$`, 'i');
function shortenColor(str) {
return str.replace(hexReg, '#$$$$' );
}
console.log(shortenColor('#336600')); // '#360'
console.log(shortenColor('#19b955')); // '#19b955'
console.log(shortenColor('#33660000')); // '#3600'
const hex = '[0-9a-z]';
const hexReg = new RegExp(`^#(? ${hex})\\k(? ${hex})\\k(? ${hex})\\k(?${hex}?)\\k$`, 'i');
function shortenColor(str) {
return str.replace(hexReg, '#$$$$' );
}
(?)
es2018 新增,具名捕获组
反向引用时的语法为 \k
在 replace 中,使用 $ 来访问具名捕获组
当应用 exec 时,具名捕获组可以通过 execResult.groups[key] 访问
const hex = '[0-9a-z]';
const hexReg = new RegExp(`^#(? ${hex})\\k(? ${hex})\\k(? ${hex})\\k(?${hex}?)\\k$`, 'i');
hexReg.exec('#33660000');
// ["#33660000", "3", "6", "0", "0", index: 0, input: "#33660000", groups: {r: "3", g: "6", b: "0", a: "0"}]
function execURL(url) {
// ……
}
console.log(execURL('https://www.360.cn'));
{
protocol: 'http:',
host: 'www.360.cn',
hostname: 'www.360.cn',
port: '',
pathname: '',
search: '',
hash: ''
}
console.log(execURL('http://localhost:8080/?#'));
{
protocol: 'http:',
host: 'localhost:8080',
hostname: 'localhost',
port: '8080',
pathname: '/',
search: '?',
hash: '#'
}
console.log(execURL('https://image.so.com/view?q=360&src=srp#id=9e17bd&sn=0'));
{
protocol: 'https:',
host: 'image.so.com',
hostname: 'image.so.com',
port: '',
pathname: '/view',
search: '?q=360&src=srp',
hash: '#id=9e17bd&sn=0'
}
console.log(execURL('this is not a url'));
{
protocol: '',
host: '',
hostname: '',
port: '',
pathname: '',
search: '',
hash: ''
}
const protocol = '(?https?:)' ;
const host = '(?(?[^/#?:]+)(?::(?\\d+))?)' ;
const path = '(?(?:\\/[^/#?]+)*\\/?)' ;
const search = '(?(?:\\?[^#]*)?)' ;
const hash = '(?(?:#.*)?)' ;
const reg = new RegExp(`^${protocol}\/\/${host}${path}${search}${hash}$`);
function execURL(url) {
const result = reg.exec(url);
if (result) {
result.groups.port = result.groups.port || '';
return result.groups;
}
return {
protocol: '', host: '', hostname: '', port: '',
pathname: '', search: '', hash: '',
};
}
console.log(execURL('https://www.360.cn'));
console.log(execURL('http://localhost:8080/?#'));
console.log(execURL('https://image.so.com/view?q=360&src=srp#id=9e17bd&sn=0'));
console.log(execURL('this is not a url'));
const host = '(?(?[^/#?:]+)(?::(?\\d+))?)' ;
……
function execURL(url) {
const result = reg.exec(url);
if (result) {
result.groups.port = result.groups.port || '';
return result.groups;
}
return {
protocol: '', host: '', hostname: '', port: '',
pathname: '', search: '', hash: '',
};
}
注意事项
port 捕获组可能为 undefined
要考虑解析失败的情形
function execUrlParams(str) {
// ……
}
console.log(execUrlParams('#')); // { }
console.log(execUrlParams('##')); // { '#': '' }
console.log(execUrlParams('?q=360&src=srp')); // { q: '360', src: 'srp' }
console.log(execUrlParams('test=a=b=c&&==&a=')); // { test: 'a=b=c', '': '=', a: '' }
function execUrlParams(str) {
str = str.replace(/^[#?&]/, '');
const result = {};
if (!str) {
return result;
}
const reg = /(?:^|&)([^&=]*)=?([^&]*?)(?=&|$)/y;
let exec = reg.exec(str);
while (exec) {
result[exec[1]] = exec[2];
exec = reg.exec(str);
}
return result;
}
console.log(execUrlParams('#')); // { }
console.log(execUrlParams('##')); // { '#': '' }
console.log(execUrlParams('?q=360&src=srp')); // { q: '360', src: 'srp' }
console.log(execUrlParams('test=a=b=c&&==&a=')); // { test: 'a=b=c', '': '=', a: '' }
const reg = /(?:^|&)([^&=]*)=?([^&]*?)(?=&|$)/y;
*?
? 可以跟在任何限定符之后,表示非贪婪模式(注意:这个例子其实不太恰当,使用贪婪模式效果是一样的)
y
es6 新增,粘连修饰符,和 g 修饰符类似,也是全局匹配。区别在于:
y 修饰符每次匹配的结果必须是连续的
y 修饰符在 match 时只会返回第一个匹配结果
其它注意事项
正则表达式如果可能匹配到空字符串,极有可能造成死循环,所以这段代码很重要:
if (!str) {
return result;
}
2)解析指定 key
这是一道课后作业题,交给大家自己来完成
function getUrlParam(str, key) {
// ……
}
console.log(getUrlParam('?nothing', 'test')); // ''
console.log(getUrlParam('#a=1&aa=2&aaa=3', 'a')); // '1'
console.log(getUrlParam('&b=1&a=1&b=2', 'b')); // '2'
console.log(getUrlParam('a=1&b=2&c=&d', 'c')); // ''
console.log(getUrlParam('&d==', 'd')); // '='
注意事项
存在多个重复的 key 时,要求只返回最后一条匹配的结果
挑战1:解法不止一种,你可以写出尽可能多的解法吗?
挑战2:可以写出尽可能短的正则表达式吗?