RegExp (正则表达式)

# 编写正则表达式

创建方式有两种

代码语言:javascript
复制
// 字面量创建方式
let reg1 = /\d+/;

// 构造函数 参数:元字符字符串;修饰符字符串
let reg2 = new RegExp("\d+"); // 注意构造函数中字 符需要转义

# 元字符和修饰符

常用元字符

量词元字符:设置出现的次数

代码语言:javascript
复制
// * 零到多次
// + 一到多次
// ? 零次或一次
// {n} 出现 n 次
// {n,} 出现 n 到多次
// {n,m} 出现 n 到 m 次

特殊元字符:单个或组合在一起代表特殊的含义

代码语言:javascript
复制
// \ 转义字符 (普通 <-> 特殊)
// . 除 \n 以为的任意字符
// ^ 以哪一个元字符作为开始
// $ 以哪一个元字符作为结束
// \n 换行符
// \d 0~9 之间的一个数字
// \D 非 0~9 之间的一个数字
// \w 数字、字母、下划线中的任意一个字符
// \W 非数字、字母、下划线中的任意一个字符
// \s 一个空白字符(包含空格、制表符、换页符等)
// \S 非空白字符
// \t 制表符(一个 Tab 键:四个空格)
// \b 匹配一个单词的边界

// x|y x 或 y 中的一个字符
// [xyz] x 或 y 或 z 中的一个字符
// [^xy] 除了 x 和 y 外的任意字符
// [a-z] 指定 a-z 范围中的任意字符 [0-9a-zA-Z_] <=> \W
// [^a-z] 除 a-z 的任意字符
// () 正则中的分组符号
// (?:) 只匹配不捕获
// (?=) 正向预查
// (?!) 负向预查

普通元字符:代表本身含义

代码语言:javascript
复制
// /cellinlab/ 匹配 cellinlab

常用修饰符

代码语言:javascript
复制
// i ingoreCase 忽略单词大小写匹配
// m multiline 可以进行多行匹配
// g global 全局匹配

# 元字符示例

^ $

代码语言:javascript
复制
let reg = /^\d/; // 以数字开始

let reg2 = /\d$/; // 以数字结束

let reg3 = /\d+/; // 包含数字即可

let reg4 = /^\d+$/; // 以数字开始且以数字结尾

// 验证手机号码
let reg5 = /^1\d{10}$/;

\

代码语言:javascript
复制
let reg = /^2.3$/; // 匹配以 2 开头,以 3 结尾,中间是任意字符的

let reg2 = /^2.3$/; // 匹配 '2.3'

let str = "\d";
let strReg = /^\d$/; // 利用 \ 将 特殊元字符 \d 转换成普通 字符
console.log(strReg.test(str)); // true

x|y

代码语言:javascript
复制
let reg = /^18|29$/;
console.log(reg.test('18')); // true
console.log(reg.test('29')); // true
console.log(reg.test('129')); // true
console.log(reg.test('189')); // true
console.log(reg.test('1829')); // true
console.log(reg.test('829')); // true
console.log(reg.test('182')); // true

// 直接 x|y 会存在很乱的优先级问题,一般写的时候会伴随着小括号进行分组,因为小括号可以改变优先级
let reg2 = /^(18|29)$/;
console.log(reg.test('18')); // true
console.log(reg.test('29')); // true
console.log(reg.test('129')); // false
console.log(reg.test('189')); // false
console.log(reg.test('1829')); // false
console.log(reg.test('829')); // false
console.log(reg.test('182')); // false

[]

代码语言:javascript
复制
// 1. [] 中出现的字符一般都代表本身的含义
let reg = /^[@+]+$/; // 
console.log(reg.test('@@')); // true
console.log(reg.test('@+')); // true

// 2. [] 中不存在多位数
reg = /^[18]$/;
console.log(reg.test('18')); // false
console.log(reg.test('1')); // true
console.log(reg.test('8')); // true

reg = /^[10-29]$/; // 1 或 0-2 或 9
console.log(reg.test('1')); // true
console.log(reg.test('9')); // true
console.log(reg.test('0')); // true
console.log(reg.test('2')); // true
console.log(reg.test('10')); // false

# 常用正则表达式

验证是否为有效数字

代码语言:javascript
复制
/**
 * 1. 可能出现 + - 号,也可能不出现 (+|-)? 或 [+-]?
 * 2. 一位 0-9 都可以,多位首位不能为 0 (\d|([1-9]\d+))
 * 3. 小数部分可能有可能没有,如果有的话后面必须有 小数点 + 数字 (\.\d+)?
 */
let reg = /^(+|-)?(\d|([1-9]\d+))(\.\d+)?$/;

验证密码

代码语言:javascript
复制
// 数字、字母、下划线 6~16位
let reg = /^\w{6,19}$/;

验证真实姓名

代码语言:javascript
复制
/**
 * 1. 汉字 /^[\u4E00-\u9FA5]$/
 * 2. 名字长度 2-10
 * 3. 汉译中间可能有 ·
 */
let reg = /^[\u4E00-\u9FA5]{2,10}(·[\u4E00-\u9FA5]{2,10}){0,2}$/;

验证邮箱

代码语言:javascript
复制
/**
 * \w((-\w+)|(\.\w+))*
 * 1. 开头是数字、字母、下划线,1 至 多位 
 * 2. 还可以是 -数字字母下划线 或 .数字字母下划线 整体零到多位
 * 邮箱的名字由“数字、字母、下划线、-、.”几部分组成,但是 . 或 - 不能连续出现也不能作为开始
 * @[A-Za-z0-9]+
 *  @ 后面紧跟着数字、字母(1-多位)
 * ((\.|-)[A-Za-z0-9]+)*
 *  对 @ 后面名字的补充 (.com.cn)
 * \.[A-Za-z0-9]+
 *  匹配最后的域名 .数字或字母 (.com .cn .edu)
 */
let reg = /^\w((-\w+)|(\.\w+))*@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/;

验证身份证号码

代码语言:javascript
复制
/**
 * 1. 一共 18 位
 * 2. 最后一位可能是 X (X 表示 10)
 * 3. 前六位:省市县 620122
 * 4. 中间八位: 年月日 YYYYMMDD
 * 5. 最后四位:
 *      最后一位 X 或数字
 *      倒数第二位 偶数 女 奇数 男
 */
// 小括号分组捕获
let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|X)$/;
reg.exec('620122199805162005');
// 捕获结果是一个数组,包含每一个小分组单独获取的内容
// ["620122199805162005", "620122", "1998", "05", "16", "0", "5", index: 0, input: "620122199805162005", groups: undefined]

# 正则捕获

实现正则捕获的方法

  • RegExp.prototype 的方法
    • exec
    • test
  • String.prototype 上支持正则表达式处理的方法
    • replace
    • match
    • split
    • 其他
代码语言:javascript
复制
// 实现正则捕获的前提是:当前正则要和字符串匹配,如果不匹配捕获结果为 null
let str = 'cell2019cellinlab2020startup2021';
let reg = /\d+/;
console.log(reg.exec(str));
// ["2019", index: 4, input: "cell2019cellinlab2020startup2021", groups: undefined]
/**
 * exec 实现的正则捕获
 *  1. 捕获的结果是 null 或 一个数组
 *      第一项:本次捕获的内容
 *      其余项:对应小分组本次单独捕获的内容
 *      index: 当前捕获内容在字符串中的索引
 *      input: 原始字符串
 *  2. 每执行一次 exec 只能捕获一个符合正则规则的,但默认情况始终下执行第一次的捕获(正则捕获的懒惰性,原因是匹配后 lastIndex 值默认不会被修改)
 *      reg.lastIndex:当前正则下一次匹配的起始索引位置
 */
reg = /\d+/g;
console.log(reg.lastIndex); // 0
console.log(reg.exec(str));
console.log(reg.lastIndex); // 8

// 实现 execAll 执行一次可以获取所有匹配结果
~ function() {
function execAll(str='') {
if (!this.global) {
return this.exec(str);
}
const arr = [];
let res = this.exec(str);
while(res) {
arr.push(res[0]);
res = this.exec(str);
}
return arr.length === 0 ? null : arr;
}
RegExp.prototype.execAll = execAll;
}();
let reg2 = /\d+/g;
console.log(reg2.execAll(str)); // ["2019", "2020", "2021"]
console.log(str.match(reg2)); // ["2019", "2020", "2021"]

# 正则捕获的贪婪性

默认安装当前正则匹配的最长结果来获取

代码语言:javascript
复制
let str = 'cell2020@cellinlab2021';
let reg = /\d+/g;
console.log(str.match(reg)); // ["2020", "2021"]
reg = /\d+?/g; // 在量词元字符后面设置 ? 取消贪婪性(按照正则匹配最短结果来获取)
console.log(str.match(reg)); //  ["2", "0", "2", "0", "2", "0", "2", "1"]

# 其他正则捕获方法

test

代码语言:javascript
复制
let str = '{0}年{1}月{2}日';
let reg = /\{(\d+)\}/g;
console.log(reg.test(str)); // true
console.log(RegExp.$1); // 0
console.log(reg.test(str)); // true
console.log(RegExp.$1); // 1
console.log(reg.test(str)); // true
console.log(RegExp.$1); // 2
console.log(reg.test(str)); // false
console.log(RegExp.$1); // 2 存储上一次捕获的结果 支持 $1-$9,即获取前九个分组信息

replace 字符串中实现替换的方法

代码语言:javascript
复制
let str = 'cell@2019|cell@2020';
// str = str.replace('cell', 'cellinlab').replace('cell', 'cellinlab'); 
// cellinlabinlab@2019|cell@2020
str.replace(/cell/g, 'cellinlab'); 
// cellinlab@2019|cellinlab@2020

应用

代码语言:javascript
复制
// => 2021年03月02日
let time = '2021-03-02';
let reg = /^(\d{4})-(\d{1,2})-(\d{1,2})$/;
// time = time.replace(reg, '$1年$2月$3日'); // 2021年03月02日
// 能捕获几次机会将函数执行几次,函数参数和 exec 捕获的内容一一致
time = time.replace(reg, (big, $1, $2, $3) => {
  console.log(big, $1, $2, $3); // 2021-03-02 2021 03 02
  return `${$1}年${$2}月${$3}日`;
}); // 2021年03月02日

首字母大写

代码语言:javascript
复制
let str = "hello world";
let reg = /\b([a-zA-Z])[a-zA-Z]*\b/g;
str = str.replace(reg, (...arg) => {
  let [word, firstLetter] = arg;
  firstLetter = firstLetter.toUpperCase();
  word = word.substring(1);
  return firstLetter + word;
}); // "Hello World"

获取字符串中出现最多的字母即次数

代码语言:javascript
复制
let str = 'cellinlab@copyright by cell';
// 排序
let str1 = str.split('').sort((a, b) => a.localeCompare(b)).join(''); //   @abbccceeghiilllllnoprtyy
let reg = /([a-zA-Z])\1+/g; // \1+ 出现和其一样的并且出现多次
let arr = str1.match(reg).sort((a, b) => b.length - a.length); // ["lllll", "ccc", "bb", "ee", "ii", "yy"]
let max = arr[0].length;
let res = [arr[0].substr(0, 1)];
for (let i = 1; i < arr.length; i++) {
  let item = arr[i];
  if (item.length < max) {
    break;
  }
  res.push(item.substr(0, 1));
}

字符串方法扩展

代码语言:javascript
复制
~function () {
  /**
   * 时间字符串格式化
   * @param {String} template 模板
   * @return 格式化后的时间字符串
   */
  function formatTime(template = '{0}年{1}月{2}日 {3}时{4}分{5}秒') {
    let timeArr = this.match(/\d+/g);
    template = template.replace(/\{(\d+)\}/g, (content, $1) => {
      let time = timeArr[$1] || '00';
      time.length < 2 ? time = `0${time}` : null;
      return time;
    });
    return template;
  }
  /**
   * queryURLParams 获取 URL 地址问号后面的参数信息,可能包含hash值
   */
  function queryURLParams() {
    // /([^?=&#]+)=([^?=&#]+)/g
    let obj = {};
    this.replace(/([^?=&#]+)=([^?=&#]+)/g, (...[, $1, $2]) => obj[$1] = $2);
    this.replace(/#([^?=&#]+)/g, (...[, $1]) => obj['HASH'] = $1);
    return obj;
  }
  /**
   * 千分符
   */
  function millimeter() {
    // /\d{1,3}(?=(\d{3})+$)/g
    return this.replace(/\d{1,3}(?=(\d{3})+$)/g, (content) => {
      return content + ',';
    });
  }
  ['formatTime', 'queryURLParams', 'millimeter'].forEach(method => {
    String.prototype[method] = eval(method);
  })
}();
let str = '2021-3-3 9:18:7';
str.formatTime(); // "2021年03月03日 09时18分07秒"
str.formatTime('{1}-{2} {3}:{4}'); // "03-03 09:18"

let url = 'http://api.cellinlab.xyz/?type=log&time=2020#blog';
url.queryURLParams(); // queryURLParams

let num = '89976611'; // 89,976,611
num.millimeter(); // "89,976,611"