那些年反覆學習的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這些強大的文本處理工具的核心都是正則表達式,

既然都看到這裏了, 點個贊吧 💗

相關文章
相關標籤/搜索