正则表达式

81 阅读5分钟

简介

正则表达式(Regular Expression)其实就是一门工具,目的是为了字符串模式匹配,从而实现搜索和替换功能。

简单来说就是:按照某种规则去匹配符合条件的字符串。正则表达式的规则是 /pattern/flags

创建正则表达式

字面量:const exp1 = /\d/g;

构造函数:const exp2 = new RegExp('\d','g');

组成元素

字符和元字符

字符:原义文本字符,即代表它原本含义的字符 元字符:元字符是在正则表达式中有特殊含义的非字母字符。则表达式中的元字符包括:( [ { \ ^ $ | ) ? * + . ] }

匹配模式

image.png

类使用 []来表达,用于匹配某个范围(集合/区间)内的字符。

image.png

预定义类

即便有了集合和区间的定义方式,如果要同时匹配多个字符也还是要一一列举,这是低效的。所以在正则表达式里衍生了一批用来同时匹配多个字符的简便正则表达式。

image.png

image.png

量词

image.png

边界

image.png

词边界

图片1.png

图片2.png 单词:在JS中, 单词的定义就是\w, 非单词的定义就是\W

边界: 不同类有界. \w\W或者\W\w中间,这个位置前后不是同类

非边界: 同类无界. \w\w 或者 \W\W中间, 这个位置前后都是同类

划重点,这里要考:词边界就是某个位置左右两边不同时是\w,非词边界就是某个位置左右两边同时都是\w或\W

单词边界例子

'He is a boy, This is a dog. Where is she?'.replace(/\bis\b/g, 'IS');

// 'He IS a boy, This IS a dog. Where IS she?'

'He is a boy, This is a dog. Where is she?'.replace(/\Bis\b/g, 'IS');

// 'He is a boy, ThIS is a dog. Where is she?'

分组

正则表达式中的 () 是用于分组的元字符,它表示将其中的表达式作为一个整体进行处理,所有以()元字符所包含的正则表达式被分为一组,每一个分组都是一个子表达式,分组在正则中运用非常广泛。

image.png

分组中使用 | 可以达到或的效果
比如:T(oo|ii)m可以匹配 ToomTiim

'abToomhaTiimmm'.replace(/T(oo|ii)m/g, '-');   // 'ab-ha-mm'

贪婪模式和非贪婪模式

正则表达式在匹配的时候默认会尽可能多的匹配,叫贪婪模式。通过在量词后加 ?可以进行非贪婪匹配 比如 \d{3,6}默认会匹配6个数字而不是3个,在量词 {}后加一个 ?就可以修改成非贪婪模式,匹配3次

image.png

捕获分组和非捕获分组

捕获分组:使用 ()的表达式可以使用 $1- $9等来匹配分组结果,$n代表第n个()的捕获记录

'2023-06-02'.replace(/(\d{4})-(\d{2})-(\d{2})/g, '$2/$3/$1');   // '06/02/2023'

非捕获分组:只匹配不存储捕获记录(结果)

'2023-06-02'.replace(/(\d{4})-(?:\d{2})-(\d{2})/g, '$2/$3/$1');  // '02/$3/2023'

回溯引用

所谓回溯引用(backreference)指的是模式的后面部分引用前面已经匹配到的子字符串(()中的内容)。你可以把它想象成是变量,回溯引用的语法像\1,\2,....,其中\1表示引用的第一个子表达式,\2表示引用的第二个子表达式,以此类推。而\0则表示整个表达式。

/\d{4}(\-|\/|.)\d{1,2}\1\d{1,2}/.test('2016-03-26');  // true

/\d{4}(\-|\/|.)\d{1,2}\1\d{1,2}/.test('2016-03.26');  // false

上面例子中的\1代表重复(\-|\/|.)的结果,(\-|\/|.)的匹配结果是 - 所以\1就代表 - ,即第二个例子结果为false

前瞻

前瞻 Lookahead 是正则匹配到规则的时候,向后检查是否符合断言。

image.png

'1a2bc*456v8'.replace(/\w(?=\d)/g, '-');   // '1-2bc*--6-8'    匹配后面是数字的单词字符

'1a2bc*456v8'.replace(/\w(?!\d)/g, '-');  // '-a---*45-v-'       匹配后面不是数字的单词字符

(?=exp)匹配一个位置,这个位置的右边能匹配表达式exp,注意这个表达式仅仅匹配一个位置,只是它对于这个位置的右边有要求,而右边的匹配是不会被放进结果的,比如用 read(?=ing)去匹配"reading",结果是"read",而"ing"是不会放进结果的。只匹配位置,不存储结果。

举个栗子,对密码应用以下限制:其长度必须介于 4 到 8 个字符之间,并且必须至少包含一个数字,正则是 /^(?=.*\d).{4,8}$/

后顾

后顾 Lookbehind 是正则匹配到规则的时候,向前检查是否符合断言。

image.png

'1a2bc*456v8'.replace(/(?<=\d)\w/g, '-');    // '1-2-c*4---8'    匹配前面是数字的单词字符

'1a2bc*456v8'.replace(/(?<!\d)\w/g, '-');   // '-a-b-*-56v-'      匹配前面不是数字的单词字符

replace方法

replace()方法用于在字符串中用一些字符替换另一些字符,或者替换一个与正则表达式匹配的子串

string.replace(regexp/substr, replacement)

regexp/substr: 必需。规定子字符串或者要替换的模式的RegExp对象。请注意,如果访值是字符串,则将它作为要检索的直接量文本模式,而不是首先被转换为RegExp对象。 replacement: 必需,string | Function。

特殊标记$

对于replace使用正则并且第二个参数为字符串时,约定了一些特殊标记$

image.png

replace方法的第二个参数为函数

'1a2bc*456v8'.replace(/\w(?=\d)/g, function(){console.log(arguments)});

图片3.png

'4737438584548545'.replace(/\d{3,5}/g, function(){console.log(arguments)})

图片4.png

'abcdbc'.replace(/(b)(c)/g, function(){console.log(arguments)})

图片5.png

结论: param1: 匹配到的字符串 param2: 匹配的子字符串(分组) . . . paramn: 匹配的子字符串(分组) param3: 匹配到的结果在字符串中的位置 param4: 原始字符串

replace方法的实际应用

首字母大写:'please make heath your first proprity'.replace(/(^|\s)([a-z])/g, function(word) {
    return word.toUpperCase();
});
去除首尾空格:'   please make heath your first proprity    '.replace(/(^\s+)|(\s+$)/g,'');
去除所有空格:'   please make heath your first proprity    '.replace(/\s+/g,'');
手机号脱敏:'15108322461'.replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2');

正则表达式常见案例分析

正数(不包含0,不能是0.0 0.00),最多2位小数

/^(0(\.((0[1-9])|([1-9]\d?)))|([1-9]\d*(\.\d{1,2})?))$/

0(\.((0[1-9])|([1-9]\d?))):第一位输入0,后面必须跟上小数点。小数点第一位如果是0,小数点第二位就必须是1-9;小数点第一位是1-9,小数点第二位就可以是任一数字即可。

([1-9]\d*(\.\d{1,2})?):第一位输入1-9,后面可以跟任意多个数字(*代表0个或多少)。(\.\d{1,2})?代表可以输入小数点也可以不输入,如果输入了小数点,那么就一定要在小数点后面跟上1位或2位数字。
8-12位的密码,至少1个大写字母,1个小写字母,1个数字和1个特殊字符

/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]{8,12}$/

(?=.*[a-z]) 表示必须包含至少一个小写字母。
(?=.*[A-Z]) 表示必须包含至少一个大写字母。
(?=.*\d) 表示必须包含至少一个数字。
(?=.*[@$!%*?&]) 表示必须包含至少一个特殊符号。
[A-Za-z\d@$!%*?&]{8,12} 表示可以包含大小写字母、数字和特殊符号,长度为 812 个字符

数字千分位分割

'1234567890'.replace(/(\d)(?=(\d{3})+$)/g, '$1,');   // '1,234,567,890'

(\d)表示捕获分组,也就是$1
(?=(\d{3})+$)表示至少一组连续的三个数字结尾,但不将其捕获到分组中

图片6.png

'1234567890'.replace(/\B(?=(\d{3})+$)/g, ',');   // '1,234,567,890'
\B表示非词边界
(?=(\d{3})+$)表示至少一组连续的三个数字结尾,但不将其捕获到分组中

图片7.png

'1234567890.345354644'.replace(/\B(?=(\d{3})+$)/g, ',');   // '1,234,567,890.345,354,644'

'1234567890.345354644'.replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ',');  // '1,234,567,890.345354644'

'1234567890'.replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ',');  // '1,234,567,890'

\B表示非词边界
(?!\d)表示xxx的后面不是数字
(?=(\d{3})+(?!\d))代表至少一组连续的三个数字并且后面没有跟着数字的字符
(?<!\.\d*)表示xxx的前面不是.和数字,至少一组连续的三个数字并且后面没有跟着数字的字符
并且前面没有.和数字

力扣58. 最后一个单词的长度

'Hello World'.replace(/^.*(\b\w+\b)\s*$/, '$1');   // 'World'

'   fly me   to   the moon  '.replace(/^.*(\b\w+\b)\s*$/, '$1');   // 'moon'

^.* 表示以任意字符开始
(\b\w+\b)表示两个词边界之间有一个或多个单词
\s*$表示以0个或多个空格结尾
$1输出第一个子项结果

项目中用到的正则

/** 正数(不包含0,不能是0.0 0.00),最多2位小数 */
export const without0TwoDecimal = /^(0(\.((0[1-9])|([1-9]\d?)))|([1-9]\d*(\.(\d{1,2}))?))$/;

/** 正数(包含0, 不能是0. 1.),最多2位小数 */
export const with0TwoDecimal = /^(0(\.(\d{1,2}))?|([1-9]\d*(\.(\d{1,2}))?))$/;

/** 正数(不包含0),最多10位数,最多2位小数 */
export const without0Length10TwoDecimal =
  /^(0(\.((0[1-9])|([1-9]\d?)))|1000000000(\.0{1,2})?|([1-9]\d{0,9}(\.(\d{1,2}))?))$/;

/** 正数(包含0),最多10位数,最多2位小数 */
export const with0Length10TwoDecimal =
  /^(0(\.(\d{1,2}))?|1000000000(\.0{1,2})?|([1-9]\d{0,9}(\.(\d{1,2}))?))$/;

/** 请输入正数(包含0),最多10位整数,2位小数 */
export const PositiveWith0AtMostTwoDecimal = {
  pattern: with0Length10TwoDecimal,
  message: '请输入,最多10位整数,2位小数',
};

/** 正数(不包含0),0位小数或输了小数点就必须2位小数 */
export const without0TwoOrZeroDecimal = /^(0(\.((0[1-9])|([1-9]\d)))|([1-9]\d*(\.(\d{2}))?))$/;

/** 正数(包含0),0位小数或输了小数点就必须2位小数 */
export const with0TwoOrZeroDecimal = /^(0(\.(\d{2}))?|([1-9]\d*(\.(\d{2}))?))$/;

/** 整数(包含正整数 0 负整数) */
export const integer = /^(\-)?(0|[1-9]\d*)$/;

/** 正整数(不包含0 负数) */
export const positiveInteger = /^[1-9]\d*$/;

/** 正整数和0(包含正数 0) */
export const positiveIntegerZero = /^(0|[1-9]\d*)$/;

/** 精确的手机号,根据各大运营商手机号码段(包含虚拟运营商) */
export const regexExactPhone =
  /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/;

/** 粗略的手机号 */
export const regexPhone = /^1[3-9]\d{9}$/;

/** 正负数字后面n位小数(但是不能是000...这种连续的0) */
export const integerSomeDecimal = (n = 2) => {
  return new RegExp(`^(\-)?(0(\.([0-9]{1,${n}}))?|([1-9][0-9]*(\.([0-9]{1,${n}}))?))$`);
};

/** 网址正则 */
export const urlRegex =
  /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\*\+,;=.]+$/;

/** 阿里云图片正则 */
export const imgRegex = /^http(s)?:\/\/.+$/;

/** 最少6个最多16个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符 */
export const pwdRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]{6,16}/;

/**
 * @desc 0或者n位的数字,m位小数
 * @param n n位的数字
 * @param m m位小数
 */
export const zeroNDigitMDecimalReg = (n = 4, m = 2) => {
  if (!m) {
    // 没有小数位的情况
    return new RegExp(`^(0|([1-9][0-9]{0,${n - 1}}))$`);
  }

  // 一位小数位的情况
  if (m === 1) {
    return new RegExp(`^(0(\\.[1-9])?|([1-9][0-9]{0,${n - 1}}(\\.[1-9])?))$`);
  }

  // 二位小数位的情况
  if (m === 2) {
    return new RegExp(
      `^(0(\\.(([1-9]{1,2})|0[1-9]))?|([1-9][0-9]{0,${n - 1}}(\\.(([1-9]{1,2})|0[1-9]))?))$`,
    );
  }

  // 二位以上小数位的情况
  return new RegExp(
    `^(0(\\.(([1-9]{1,${m - 1}})|([0-9]{1,${m - 1}}[1-9]?)))?|([1-9][0-9]{0,${
      n - 1
    }}(\\.(([1-9]{1,${m - 1}})|([0-9]{1,${m - 1}}[1-9]?)))?))$`,
  );
};

/**
 * @desc 0或者n位的整数
 * @param n n位的数字
 * @param with0 是否包含0
 */
export const nDigitReg = (n = 4, with0?: boolean) => {
  if (with0) {
    // 包含0的情况
    return new RegExp(`^(0|([1-9][0-9]{0,${n - 1}}))$`);
  }

  // 不包含0的情况
  return new RegExp(`^[1-9][0-9]{0,${n - 1}}$`);
};

/**
 * @desc 1-9xxxx  n个9
 * @param n n位,一共多少位数字
 */
export const onetonnine = (n = 3) => {
  return new RegExp(`^[1-9][0-9]{0,${n - 1}}$`);
};

/**
 * @desc 0-9xxxx  n个9
 * @param n n位,一共多少位数字
 */
export const zerotonnine = (n = 3) => {
  return new RegExp(`^(0|([1-9][0-9]{0,${n - 1}}))$`);
};

/**
 * @desc 0-9xxxx  n个9, 最多两位小数
 * @param n n位,一共多少位数字
 */
export const zerotonnine2Decimal = (n = 4) => {
  return new RegExp(
    `^(0(\\.(([1-9]{1,2})|0[1-9]))?|([1-9][0-9]{0,${n - 1}}(\\.(([1-9]{1,2})|0[1-9]))?))$`,
  );
};

/**
 * @desc 1-9xxxx  n个9, 最多两位小数
 * @param n n位,一共多少位数字
 */
export const onetonnine2Decimal = (n = 4) => {
  return new RegExp(`^[1-9][0-9]{0,${n - 1}}(\\.(([1-9]{1,2})|0[1-9]))?$`);
};

// 请输入1~365的正整数
export const PositiveInteger1To365 = {
  pattern: /^(36[0-5]|3[0-5]\d|[1-2]\d\d|[1-9]\d|[1-9])$/,
  message: '请输入1~365的整数',
};