近幾日對本身一直不太擅長的正則表達式作了一次全面的掃盲。心疼本身之餘仍是有一些收穫吧,在這裏作一個比較零散的總結,整理一些對理解正則比較有利的點。
你沒有看錯,就是黑人問號中的問號,這個字符在正則裏面算是一個入門中很容易被帶偏的點了。首先要知道什麼是正則中的量詞。node
在正則中,一般要表示一個表達式匹配的數量,這個時候量詞就登場了。
主要會使用如下幾個量詞正則表達式
/(\w)*/.exec(str) // 匹配任意次 /(\w)+/.exec(str) // 匹配一次到屢次 /(\w)?/.exec(str) // 匹配零到一次(記住這裏問號的用法!) /(\w){2, 4}/.exec(str) // 匹配兩次到四次 /(\w){2, }/.exec(str) // 匹配兩次以上
咱們能夠發現,在這裏"?"做爲一個量詞來使用,表示匹配零到一次。chrome
接下來要理解下一個概念:貪婪匹配code
搜了一下wiki,貌似沒有相關的詞條,通俗的解釋,貪婪匹配模式下,會盡量多地匹配知足條件的字符。而正則默認是貪婪模式的。ci
舉個例子。好比我要匹配"suuuuuuuuuuck"字符中的s和k中間的字符。並無什麼問題。unicode
let result = "suuuuuuuuuuck".match(/s(.*)k/)[1] // uuuuuuuuuuc
可是我如今要搞事情,要你在"suuuuuuuuuuck duck"字符串中匹配相同的字段,一樣的表達式會匹配到如下的結果,由於此時的正則是貪婪的,它必定會匹配到沒法匹配的時候才休止。字符串
// uuuuuuuuuuck duc
這時候就須要手動開啓非貪婪模式了get
let result = "suuuuuuuuuuck duck".match(/s(.*?)k/)[1] // uuuuuuuuuuc
區別是在量詞後加了個問號,這時候該捕獲組就算是開啓了非貪婪模式了。input
按照上面的理解,若是你是一個新手,確定會有所疑惑,量詞(*)後面跟着一個量詞(?),這是什麼鬼意思,這麼反人類的?產品
其實,這裏就涉及到"?"的第二個用法了,即當它跟在一個量詞背後的時候,表示該表達式開啓了非貪婪模式,即對錶達式儘量少地匹配結果。因此,對應的,配合量詞使用,會產生如下結果。
思考題:因此,"??" 應該如何匹配呢?
正則匹配除了驗證一個字符串是否符合條件外,還能夠從中提取咱們所須要的信息。好比,咱們獲得了一個"新中國成立於1949-10-01"的字符串,做爲一個愛國人士,咱們要把這個年月日提取出來謹記於心。因此我寫了一個正則,得到的結果以下
這裏提取的操做就須要經過小括號進行捕獲。正則會默認對捕獲組分配組數。
"新中國成立於1949-10-01".match(/(\d{4})-(\d{2})-(\d{2})/) // ["1949-10-01", "1949", "10", "01", index: 6, input: "新中國成立於1949-10-01", groups: undefined]
咱們也能夠忽略某些分組"(?:exp)",這樣正則就不會爲爲其分配組數。
"新中國成立於1949-10-01".match(/(\d{4})-(\d{2})-(?:\d{2})/) // ["1949-10-01", "1949", "10", index: 6, input: "新中國成立於1949-10-01", groups: undefined]
假如咱們有一個疊詞判斷的需求,驗證一個詞語是否是"ABA"格式的,咱們能夠這麼作
// 首先漢字的unicode範圍是\u4e00-\u9fa5 // 這裏咱們首先對第一個字符進行了捕獲,組數爲1 // 而後咱們後面經過"\1"的方式去複用捕獲組,這樣就意味着匹配到了相同的字符,也就達到了限制的目的。 /([\u4e00-\u9fa5])[\u4e00-\u9fa5]\1/.test("是否是") // 固然是true
要記住下標對人類來講仍是挺麻煩的,能夠說徹底沒啥可讀性,固然正則也提供了爲分組命名的方式
"新中國成立於1949-10-01".match(/(?<year>\d{4})-(?<month>\d{2})-(?<date>\d{2})/) // 咱們能夠發現,這時候捕獲組不只擁有組數,同時groups屬性不爲空了。 // ["1949-10-01", "1949", "10", "01", index: 6, input: "新中國成立於1949-10-01", groups: {…}] // 展開groups 是這樣的 // {year: "1949", month: "10", date: "01"} /** 固然命名捕獲組也是可使用的 */ // (?<name>exp) 命名捕獲組 // \k<name> 引用 // 仍是疊詞的那個例子 /(?<thx>[\u4e00-\u9fa5])[\u4e00-\u9fa5]\k<thx>/.test("是否是")
如今有一個需求,匹配出英文語句"I'm singing while you're dancing"中全部帶有ing後綴的單詞(不包含ing)。要想拿到danc 和 sing,咱們須要用到零寬斷言。
零寬斷言用於查找某些內容以前或以後的東西,只指定一個位置,自己並不佔據字符,這也是爲何咱們稱之爲零寬度
對於表達式表示確定,咱們稱之爲正向,反之稱之爲負向,(注意,這裏的正負指的是對條件的確定和否認,而不是匹配的方向。)
而對於匹配的方向而言,咱們有另一種稱呼,對向後匹配的稱之爲預測先行,向前匹配的稱之爲回顧後發
因此,對應的四種組合分別是
目前的js引擎對回顧後發斷言的實現還不徹底,就我所知在chrome能成功使用,可是在nodejs環境下是不識別的。
如今咱們從引言中的例子來實踐一下
"I am singing while you're dancing".match(/\b([a-zA-Z]+)(?=ing\b)/g) // 咱們忽略前面不知足的匹配,直到index = 4時,s爲單詞邊界,知足條件 // 而第一個捕獲組是貪婪的,他會首先匹配到整個singing,而後將掌控權交給(?=ing\b),singing不知足匹配 "singinging" // 因而開始回溯到單詞 singin,繼續斷言, 匹配到的下一個字符爲"g", 不知足"singining", 又開始回溯到"singi"... // 直到回溯到"sing"時,斷言後面有一個ing,而且是一個單詞邊界,因而"singing"知足條件,這時候咱們的正則匹配到了第一個結果。 // 因爲零寬斷言是不消費字符的,因此咱們獲得整個表達式匹配的第一個結果是"sing" // 因而引擎以一樣的方式向後面的位置查找,獲得了danc // ["sing", "danc"]
咱們如今看一下怎麼使用負向斷言,假如咱們有一個系統,3月25號要進行維護,不能使用了,這時候有用戶要辦理業務,選擇日期的時候咱們要過濾3月25日這一天,因此產品經理要你臨時加上一條規則限定。
選擇後日期輸出的格式是"yyyy-mm-dd",這時候咱們能夠這麼寫正則
/(?!2018-03-25)(\d{4})-(\d{2})-(\d{2})/.test("2018-03-11") // true 經過驗證 /(?!2018-03-25)(\d{4})-(\d{2})-(\d{2})/.test("2018-03-25") // false
用(?<=exp) 找出 "beep name=wanglihong abcdefg"
"beep name=wanglihong abcdefg".match(/(?<=\bname=)(\w+\b)/) // ["wanglihong", "wanglihong", index: 10, input: "beep name=wanglihong abcdefg", groups: undefined]
提取a標籤的屬性的同時,經過(?<!exp) 過濾style屬性
var template = '<a href="/bee" target="_blank" id="o" style="color: black;">點擊跳轉</a>' template.match(/(\w+)=(?<!style=)"([^"]+)"/g) // [href="/bee", target="_blank", id="o"]
摸透了零寬斷言,正則的能力也就算上了一個臺階了,固然還有平衡組這種操做,由於在js不支持,因此就暫時不討論了。