「JavaScript 正则表达式迷你书.pdf」https://www.aliyundrive.com/s/i77Fq6HocdT

匹配 16 进制颜色

  • 匹配 6 位的 16 进制

    const regex = /#[0-9a-fA-F]{6}/
  • 颜色也有可能是 3 位的,由于管道符也是贪婪匹配,所以需要先匹配 6 位再匹配 3 位

    const regex = /#[0-9a-fA-F]{6}|[0-9a-fA-F]{3}/

匹配时间

  • 匹配小时,00-19,20-23

    const regex = /([01][0-9]|2[0-3])/
  • 匹配分钟,00-59

    const regex = /[0-5][0-9]/
  • 最终结果

    const regex = /^([01][0-9]|2[0-3]):[0-5][0-9]$/

匹配日期

  • 匹配年份,0000-xxxx

    const regex = /\d{4}/
  • 匹配月份,01-12

    const regex = /(0[1-9]|1[0-2])/
  • 匹配日,01-31

    const regex = /0[0-9]|[12][0-9]|3[01]/
  • 最终结果

    const regex = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/

匹配文件路径

  • 匹配磁盘

    const regex = /[a-zA-Z]:\\/
  • 匹配文件夹

    const regex = /[^\\:*<>|"?\r\n/]+\\/
  • 完整路径

    const regex = /^[a-zA-Z]:\\([^\\:*<>|"?\r\n/]+\\)*([^\\:*<>|"?\r\n/]+?$)/

位置:

  • ^: 开头
  • $: 结尾
  • \b: 单词边界,\w\W 之间的位置(\w === [0-9a-zA-Z_] 数字字母下划线)
  • \B: 非单词的边界,
  • (?=p): 以 p 结尾
  • (?!p): 不以 p 结尾

数字千分位分隔符

  • 添加最后一位的逗号

    "123456789".replace(/?=\d{3}$/g, ",")
    // => "123456,789"
  • 添加所有逗号

    "123456789".replace(/(?=(\d{3})+$)/g, ",")
    // => ",123,456,789"
  • 去除开头的逗号

    "123456789".replace(/(?!^)(?=(\d{3})+$)/g, ",")
    // => "123,456,789"
  • 多组数字格式化

    "123456789 123456789".replace(/(?!\b)(?=(\d{3})+\b)/g, ",")
    // 等价于 =>
    "123456789 123456789".replace(/\B(?=(\d{3})+\b)/g, ",")
    // => "123,456,789 123,456,789"

验证密码

  • 必须包含数字

    const regex = /(?=.*[0-9])/
  • 必须包含汉字

    const regex = /(?=.*[\u4e00-\u9fa5])/
  • 必须包含数字和字母

    const regex = /(?=.*[0-9])(?=.*[a-zA-Z])/
  • 必须包含数字和汉字

    const regex = /(?=.*[0-9])(?=.*[\u4e00-\u9fa5])/
  • 必须包含字母和汉字

    const regex = /(?=.*[a-zA-Z])(?=.*[\u4e00-\u9fa5])/
  • 必须包含数字、字母和汉字,且至少包含两种字符,两两组合,那么组合数就是 ,三种情况

    const regex = /(?=.*[0-9])(?=.*[a-zA-Z])|(?=.*[0-9])(?=.*[\u4e00-\u9fa5])|(?=.*[a-zA-Z])(?=.*[\u4e00-\u9fa5])/
  • 再加一个位数限制:最低 6 位,最高 20 位

    const regex = /((?=.*[0-9])(?=.*[a-zA-Z])|(?=.*[0-9])(?=.*[\u4e00-\u9fa5])|(?=.*[a-zA-Z])(?=.*[\u4e00-\u9fa5]))^[0-9a-zA-Z\u4e00-\u9fa5]{6,20}/
  • 如果再加两种字符:下划线( _ )、短杠( - ),要是采用和上面相同的方式:两两组合, == 10,意味着要写 10 种情况,工程量可想而知。换个角度,至少包含两种字符的对立事件就是:不能全都是数字、字母和汉字

    • 不能全为数字

      const regex = /(?!^[0-9]+$)/
    • 不能全都是数字、字母和汉字

      const regex = /(?!^[0-9]+$)(?!^[a-zA-Z]+$)(?!^[\u4e00-\u9fa5]+$)/
    • 最终版

      const regex = /(?!^[0-9]{6,20}$)(?!^[a-zA-Z]{6,20}$)(?!^[\u4e00-\u9fa5]{6,20}$)(?!^-{6,20}$)(?!^_{6,20}$)^[0-9a-zA-Z\u4e00-\u9fa5\-_]{6,20}/

格式化日期(分组引用)

  • yyyy-mm-dd 格式化成 mm/dd/yyyy

    const regex = /(\d{4})-(\d{2})-(\d{2})/
    const date = "2022-08-10"
    const result = date.replace(regex, "$2/$3/$1")
    // => "08/10/2022"
    
    // 等价于 =>
    const result = date.replace(regex, function () {
      return `${RegExp.$2}/${RegExp.$3}/${RegExp.$1}`
    })
    
    // 等价于 =>
    const result = date.replace(regex, function (match, year, month, day) {
      return `${month}/${day}/${year}`
    })

匹配多种日期格式(反向引用)

  • 要匹配:yyyy.mm.ddyyyy-mm-ddyyyy/mm/dd

    const regex = /\d{4}(\.|-|\/)\d{2}\1\d{2}/
  • \10 表示第 10 个分组,若想匹配 \10,请使用 (?:\1)0 或者 \1(?:0)(?:p) 表示非捕获括号,不会对括号中的内容进行分组);
  • 若分组 \5 不存在,则匹配 "\5",相当于对 "5" 进行了转义;
  • 若分组后带有量词,则匹配最后一次捕获的结果。

模拟字符串 trim 方法

  • 匹配开头结尾的空白符,然后替换为空(效率更高)

    function trim(str) {
      return str.replace(/^\s+|\s+$/g, "")
    }
  • 匹配整个字符串,然后用引用的部分替换整体

    function trim(str) {
      return str.replace(/^\s*(.*?)\s*$/, "$1")
    }

单词首字母大写

function firstUppercase(str) {
  return str.toLowerCase().replace(/(?:^|\s)\w/g, c => c.toUpperCase())
}
// => 如果添加修饰符 `g`,则是将所有单词首字母大写
// => 如果不加修饰符 `g`,则只会将第一个单词的首字母大写

驼峰化

function camelize(str) {
  return str.replace(/[-_\s]+(.)?/g, (match, c) => {
    return c ? c.toUpperCase() : ""
  })
}

短杠化

function kebablize(str) {
  return str
    .replace(/([A-Z])/g, "-$1")
    .replace(/[-_\s]+/g, "-")
    .toLowerCase()
}

匹配成对标签

  • 匹配开标签

    const regex = /<[^>]+>/
  • 匹配闭标签

    const regex = /<\/[^>]+>/
  • 完整正则

    const regex = /<([^>]+)>[\s\S]*<\/\1>/

需要转义的元字符

^$.*+?|\/()[]{}=!:-,,这些字符串匹配时要加 \

匹配 IPV4 地址

const regex = /^((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])$/

匹配固定电话

要匹配:0551888888880551-88888888(0551)88888888,前四位为区号,后八位为号码

  • 第一种的匹配规则

    const regex = /0\d{3}[1-9]{8}/
  • 第二种的匹配规则

    const regex = /0\d{3}-[1-9]{8}/
  • 第三种的匹配规则

    const regex = /\(0\d{3}\)[1-9]{8}/
  • 结合第一种和第二种

    const regex = /0\d{3}-?[1-9]{8}/
  • 三种结合的最终匹配规则

    const regex = /(0\d{3}-?|\(0\d{3}\))[1-9]{8}/

匹配浮点数

要匹配:

  • 情况一:1.22+1.22-1.22

  • 情况二:10+10-10

  • 情况三:.2+.2-.2

  • 情况一:

    const regex = /^[+-]?\d+\.\d*$/
  • 情况二:

    const regex = /^[+-]?\d+$/
  • 情况三:

    const regex = /^[+-]?\.\d+$/
  • 最终结果

    const regex = /^[+-]?(\d+\.\d*|\d+|\.\d+)$/

提升效率

  • 使用具体型字符组来代替通配符,消除回溯

    匹配 123"456"789 中的 "456"

    - /".*"/
    + /"[^"]*/
  • 使用非捕获分组

    捕获分组,意味着需要额外的内存来保存分组的结果,对于不需要分组引用或反向引用的采用非捕获分组

    - /^\d+("[^"]*")\d+$/
    + /^\d+(?:"[^"]*")\d+$/
  • 独立出确定字符:可以减少回溯,加快匹配速度

    - /a+/
    + /aa*/
  • 提取分支公共部分

    - /^abc|^def/
    + /^(?:abc|def)/
    
    - /this|that/
    + /th(?:is|at)/
  • 减少分支数量,缩小它们的范围,不过降低了可读性

    - /red|read/
    + /rea?d/

replace 的应用

  • 当第二个参数是字符串时,以下字符有特殊含义

    属性 描述
    $1,$2,···,$99 匹配第 1-99 个分组里捕获的文本
    $& 匹配到的字符文本
    $` 匹配到的字符左边文本
    $’ 匹配到的字符右边文本
    $$ 美元符号
  • 当第二个参数是函数时,函数里的回调参数与使用 match(非全局匹配) 匹配到的结果类似

    "123abc456".match(/(\d+)[^\d]*(\d+)/)
    
    // => ['123abc456', '123', '456', index: 0, input: '123abc456', groups: undefined]
    
    // 第一个参数:匹配到的结果
    // 第二个参数:分组一的结果
    // 第三个参数:分组二的结果
    // 若还有多个分组,则列完分组结果...
    
    // 倒数第三个参数:匹配的开始索引
    // 倒数第二个参数:原字符串
    // 倒数第一个参数:没有修饰符 g,所以为 undefined
    "123abc456".replace(/(\d+)[^\d]*(\d+)/, function (match, $1, $2, index, input) {
      console.log([match, $1, $2, index, input])
      // => ['123abc456', '123', '456', 0, '123abc456']
    })
获取当前日期(yyyy-MM-dd HH:mm:ss)
function getCurrentDate() {
  return new Date()
    .toLocaleString()
    .replace(/\/(\d)(?=(\/|\s))/g, "-0$1")
    .replace(/\//g, "-")
}

模拟 getElementByClassName

function getElementByClassName(className) {
  const elements = document.getElementByTagName("*")
  const regex = new RegExp(`(^|\\s)${className}(\\s|$)`)
  const result = []
  for (let i = 0; i < element.length; i++) {
    const element = elements[i]
    if (regex.test(element.className)) {
      result.push(element)
    }
  }
  return result
}

封装一个类型检验工具函数

const utils = {}
"Boolean|String|Number|Function|Array|Date|RegExp|Object|Error".split("|").forEach(item => {
  utils[`is${item}`] = obj => {}.toString.call(obj) === `[object ${item}]`;
})

console.log(utils.isArray([1,2])); // true
console.log(utils.isArray({})); // false

压缩字符串

function compress(str) {
  const keys = {}
  str.replace(/([^=&]+)=([^&]*)/g, function (full, key, value) {
    keys[key] = (keys[key] ? `${keys[key]},` : "") + value
  })
  const result = []
  for (const key in keys) {
    result.push(`${key}=${keys[key]}`)
  }
  return result.join("&")
}

console.log(compress("a=1&b=2&a=3&b=4"))
// => "a=1,3&b=2,4"