那些年反复学习的JS正则表达式

Some people, when confronted with a problem, think ‘I know, I’ll use regular expressions.’ Now they have two problems. Jamie Zawinskijavascript

以前学正则表达式的过程就是一个反复学, 反复忘的过程, 感受某个点须要正则表达式来解决, 但实际编写正则表达式的时候又去查一遍, 又去从新思考一遍, 本文对正则表达式进行一个回顾, 加深对正则表达式理解以及记忆。前端

什么是正则表达式

正则表达式是用来匹配字符串的一种工具, 当咱们须要匹配指定字符时, 就可能用的到正则表达式。还记得小时候在windows上搜索文件时会输入*.exe找电脑上的游戏, 不知不觉的使用了*这个正则表达式, 用来匹配任意字符。java

试想咱们须要提取下面一段编号, 实现String.prototype.split('-')的效果正则表达式

0101-1232-2123
复制代码

咱们须要3个步骤express

  1. 先匹配出模型, 也就是连续4个数字, 使用-分隔。
/\d{4}\-\d{4}\-\d{4}/.test('0101-1232-2123')
复制代码
  1. 提取出3组数字
/(\d{4})\-(\d{4})\-(\d{4})/.exec('0101-1232-2123')
复制代码
  1. 将结果转化为一个数组
const result = Array.from(/(\d{4})\-(\d{4})\-(\d{4})/.exec('0101-1232-2123')).slice(1);
// [0101, 1232, 2123]
复制代码

有了上面的基础让咱们来提取一个时间, 好比如今时间是8:17:23windows

先分析格式: 在表述时间数字时若是是6点, 咱们能够表述为06或者6, 也有可能由1开头,好比10点,也有可能由2开头好比21点。咱们须要使用或 | 来列出全部可能的时间, 因而咱们匹配出上面8的模式为数组

/[0-9]|0[0-9]|1[0-9]|2[0-3]/
复制代码

匹配出模式后, 咱们使用()将匹配到的字符提取出来而且用^和$作头尾限定bash

const reg = /^(0[0-9]|1[0-9]|2[0-3]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])$/;
复制代码

最后将它转换成数组框架

Array.from(reg.exec('11:17:23')).slice(1)

// [8, 17, 23]
复制代码

这样提取出来使用并非很方便, ES2018支持命名提取的功能, 好比咱们能够直接使用result.group.second获取23。命名提取语法是在提取的分组前加上?运维

const reg = /^(?<h>0[0-9]|1[0-9]|2[0-3]|[0-9])\:(?<m>0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])\:(?<s>0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])$/;

const result = reg.exec('8:17:23') 

result.groups // {h: "11", m: "17", s: "23"}
复制代码

命名与提取的内容组合成了对象, 咱们能够直接经过 result.groups.h的方式去访问提取内容。

“提取” 操做, 除了使用exec以外, 还可使用match方法, 好比咱们提取下面字符串中的{{var}}变量

var str = `name: {{name}}, age: {{age}}, sex: {{sex}}`

str.match(/\{\{\w+\}\}/g) // output: ["{{name}}", "{{age}}", "{{sex}}"]
复制代码

模板引擎

模版引擎在在Web框架中占有举足轻重的地位, 各类模版引擎层出不穷pug, handlebars, ejs, 模版引擎就是让字符串能够进行一些逻辑表达, 实现这个功能的核心就是正则表达式, 让咱们看一个最简单的例子: 替换字符串中的变量, 咱们如今有字符串I am {name}, and {age} years old, 咱们想替换字符串中的{name}{age}怎么办?

function tpl2string(tpl, data){
  let reg = /(\{\w+\})/g
  return str = str.replace(reg, function(){
    return data[arguments[1].slice(1, -1)]
  })
}

tpl2string('I am {name} and {age} years old', {
  name: 'max',
  age: '15'
})

// output: "I am max and 15 years old"
复制代码

匹配行为

正则表达式默认是贪婪匹配, 也就是尽量匹配多的字符, 举一个例子:

your time is limited 这句话有3个空格, 咱们若是想匹配空格以前的字符

/.+\s/.exec('your time is limited')[0]

// your time is
复制代码

它默认匹配到了最后一个空格前面的字符your time is, 若是不想让它进行贪婪匹配, 咱们只想匹配到第一个空格以前的字符your咱们就须要使用?来进行非贪婪匹配

/.+?\s/.exec('your time is limited')[0]

// your 
复制代码

下面让咱们限定一下匹配, 若是要匹配博客的顶级域名也就是提取https://evle.netlify.com/netlify-usage中的netlify。

const s = `https://evle.netlify.com/netlify-usage`
/(\w+)\.com/.exec(s)[1]

// output: netlify
复制代码

咱们也可使用更精准的限定: 正向否认查找, 字符串后面必须跟着.com, 语法是x(?=y)

/\w+(?=\.com)/.exec(s)[0] output: netlify
复制代码

咱们一样能够匹配3.1415926中小数点前面的数字和后面的数字

/\d+(?=\.)/.exec("3.1415926")[0]  // .以前
/\d+(?!\.)/.exec("3.1415926")[0]  // .以后 语法 x(?!y)
复制代码

替换

用正则表达式匹配后替换成指定字符是很常见的场景, 好比

'papa'.replace(/p/g, 'm'); // output: mama
复制代码

再好比替换2个字符的位置, 咱们能够用$1, $2这样的方式来暂存匹配到的变量而后进行交换

"Liskov, Barbara\nMcCarthy, John\nWadler, Philip"
    .replace(/(\w+), (\w+)/g, "$2 $1"));

//output:
// Barbara Liskov
// John McCarthy
// Philip Wadler
复制代码

咱们也可使用函数来在替换时加入一些逻辑, 如前面模版引擎中那样的使用方式

let stock = "1 lemon, 2 cabbages, and 101 eggs";
function minusOne(match, amount, unit) {
  amount = Number(amount) - 1;
  if (amount == 1) { // only one left, remove the 's'
    unit = unit.slice(0, unit.length - 1);
  } else if (amount == 0) {
    amount = "no";
  }
  return amount + " " + unit;
}
console.log(stock.replace(/(\d+) (\w+)/g, minusOne));
// → no lemon, 1 cabbage, and 100 eggs
复制代码

此外正则表达式是能够动态建立的好比:

let name = "harry";
let text = "Harry is a suspicious character.";
let regexp = new RegExp("\\b(" + name + ")\\b", "gi");
console.log(text.replace(regexp, "_$1_"));
// output: _Harry_ is a suspicious character.
复制代码

The lastIndex

正则表达式中的lastIndex的属性时常让人疑惑, lastIndex的意图是让咱们可以选择匹配字符时的起点, 咱们能够更改lastIndex来调整匹配字符的起点

let reg = /\d+/g
reg.lastIndex = 3;
复制代码

须要注意的是当正则表达式开启gy模式时, lastIndex才生效, 上面表示从第三个下标开始搜索, 可是它有一个反作用

var str="JavaScript";
var reg=/JavaScript/g;

console.log(reg.test(str));  // true
console.log(reg.test(str));  // false
复制代码

缘由是由于若是找到匹配的字符, reg.lastIndex的值已经变为10, 从下标10开始找固然找不到了, 因此若是想这样使用须要手动将lastIndex设置为0

reg.test(str)
reg.lastIndex = 0
reg.test(str)
复制代码

看这怪异的使用方式, 咱们应该就意识到使用场景错了。 对, 要优雅

var str="JavaScript";
var find = "JavaScript"

str.includes(find)
复制代码

解析文件

ini是windows上的传统配置文件, 让咱们用正则表达式写一个ini文件解析器来解析咱们须要使用的数据

function parseINI(string) {
  let result = {};
  let section = result;
  // 逐行解析
  string.split(/\r?\n/).forEach(line => {
    let match;
    // 匹配 key=value类型
    if (match = line.match(/^(\w+)=(.*)$/)) {
      section[match[1]] = match[2];
    // 匹配[address]类型
    } else if (match = line.match(/^\[(.*)\]$/)) {
      section = result[match[1]] = {};
    } else if (!/^\s*(;.*)?$/.test(line)) {
      throw new Error("Line '" + line + "' is not valid.");
    }
  });
  return result;
}

// 测试文件
const config = ` name=Vasilis [address] city=Tessaloniki`

parseINI(config)

// output
// {name: "Vasilis", address: {city: "Tessaloniki"}}
复制代码

写在最后的

正则表达式不管在前端仍是运维都是很是重要的存在, 虽然正则表达式的纯文本解析会有必定性能上的问题, 但针对使用的场景无疑是利器, grep, awk, sed这些强大的文本处理工具的核心都是正则表达式,

既然都看到这里了, 点个赞吧 💗

相关文章
相关标签/搜索