淺談JavaScript正則表達式

這些是本人在 github.pages 上寫的博客,歡迎你們關注和糾錯,本人會按期在github pages 上更新。有想要深刻了解的知識點能夠留言。jquery

同時,這是本人第一次寫文章,若有目錄結構不合理,還請指出。ios

前言

剛開始學習 JS 時,正則表達式一直是我不肯意麪對的,每次讀到有關正則表達式的時候,都會避而遠之。但是,一次,當我打開 JQ 源碼的時候,發現裏面有大量的正則表達式。因而乎,本身就強迫本身學習正則,學習的過程仍是蠻愉快的。最後,真香定律終於出現了。哈哈哈!!git

這篇教程我會由淺入深的來和你們分享正則表達式,讓你們即學習到正則表達式的用法,也瞭解其在 JS 中表現的鮮爲人知的一面。es6

概述

正則表達式是 JS 中很重要的一環。也是對不少人比較不肯意麪對的一個知識點。可是,當咱們真正掌握了正則表達式,能夠利用其在咱們的代碼中發揮很大的威力,大大的簡化咱們的代碼。對於喜歡閱讀一些庫源碼的夥伴。這個真的是必須掌握的。github

固然,正則基本在每一個語言都有實現。雖不能說都相同。可是基本上都是大同小異。此外,正則表達式的範圍很是的廣,這裏也不可能每一個知識點都會涉及到。這裏,做者會將一些我認爲常見的,重要的,常見的注意點給你們一一分析。正則表達式

正則表達式基礎概念

定義

正則表達式,英文叫作 Regular Expression。按照英文字面意思解釋,就是有規則的表達式,正則表達式就是由一些列的語法規則組成的字符串,而後按照這種組合的規則去匹配一些字符串,篩選出符合條件的字符串。數據庫

知識瞭解:根據 ECMA5 規範,JS 中正則表達式的格式和功能是以 perl 語言爲藍本的。express

咱們平時寫的正則表達式很不直觀,這裏推薦一個在線工具。該工具以可視化的界面來描述咱們寫的正則表達式。工具比較簡單,你們自行了解。 在線工具編程

正則表示方法的區別

正則表達式的表示方法有兩種bash

  1. 字面量表示法
  2. 構造函數表示法
let reg = /text/ig

    let regExp = new RegExp('text', ig)

複製代碼

兩種表示方法均可行。區別在於:利用構造函數進行表示的時候,能夠動態的構建正則表達式的規則。

let str = String('****')
    // 如 let className = str + 'name'
    regExp = new RegExp(className, 'ig')

複製代碼

正則表達式的組合規則太多了,你們能夠去看一下 ECMA 規範。下面咱們就介紹一些經常使用的,常常遇到的狀況。

正則表達式組成元素

正則表達式的組成通常由如下幾類構成

  1. 原義文本字符
  2. 元字符
  3. 修飾詞

修飾詞

在 JavaScript 中,全局修飾詞有 g、i、m、y、u、s

這些修飾詞在正則表達式的匹配中,起到了很重要的做用。

g: 表明全局匹配,當匹配到目標字符串的時候,不會中止匹配,而會繼續匹配。直到匹配結束爲止。

i: 匹配的過程當中,忽略大小寫

m: 換行匹配。字符串能夠換行,若是當前行沒有匹配到,能夠換行繼續匹配

y: 執行「粘性」搜索,匹配從目標字符串的當前位置開始

u: 至關於將匹配模式轉換成 unicode 模式,能夠正確處理大於\uFFFF的 Unicode 字符。你們能夠自行參考 瞭解

s: 咱們知道 元字符 . 表明除了回車換行符之外的全部字符,可是加上 s 修飾符後, . 能夠匹配任意字符

/./.text('\n')   // false
    /./s.test('\n')  // true

    //其實還有另外一種技巧能夠匹配全部字符
    /[^]/.test('\n') // true
複製代碼

y 表明什麼含義? 這個等會再作解釋,先簡單說下,這個與正則表達式的一個屬性 lastlndex 有關係,如今解釋,可能會一臉懵逼。 下面會與 g 標誌 一塊兒討論。

元字符

元字符是在正則表達式中有特殊含義的非字母字符。這些元字符使得正則表達式的組合規則十分強大。

// 引用自規範原文 咱們能夠看一下這些元字符都有哪些,咱們應該很熟悉這其中表明的含義。
PatternCharacter :: SourceCharacter but not any of:
^ $ \ . * + ? ( ) [ ] { } |

此外,還有一些字符,是組合而成的,表明特殊的含義
有 \b、\B、\d、\D、\w、\W、\s、\S、\f、\v、\t、\n、\r 等等。這些字符表明的含義,你們自行了解,這裏不一一講解這些元字符的含義。
下面舉例子會用到一些,會對其含義作說明。
複製代碼

既然元字符表明這麼多的含義,那麼咱們若是須要在字符串中匹配這些字符怎麼辦呢? 這個時候,可使用轉義字符幫忙。

舉個栗子:

reg = /\d/ // \d 是元字符,表示數字,這個正則表達式只能匹配數字,若是咱們須要匹配'\d'呢
    reg.test('\d') // false
    這時候須要用轉義字符轉義
    reg = /\\d/ // \ 表明轉義字符
    reg.test('\d') // 仍是false
    這個爲何仍是 false 呢,不是按照規矩辦事嗎?
    這是由於 JS 裏的字符串也有轉義字符!
    能夠試一下 '\d'.length 等於1,這個時候要想匹配 '\d' 必需要在字符串中對其轉義
    reg.test('\\d') // true

複製代碼

總結:遇到這種狀況,別老是忙着爲正則表達式轉義,還得爲字符串轉義,關於字符串裏面的轉義字符,這超過了本篇討論範圍,你們自行了解。

常見元字符的解釋

下面介紹一些經常使用的元字符

元字符 表示及含義 解釋
. /[ ^\r\n ]/ 除了回車換行之外的所有字符
\d /[0-9]/ 數字字符
\D /[^0-9]/ 非數字字符
\w /[0-9a-zA-Z_]/ 單詞字符(數字,字母,下劃線)
\W /[^0-9a-zA-Z_]/ 表明非單詞字符
\s /[\f\n\r\t\v\u00a0\u1680\u180e
\u2000-\u200a\u2028\u2029\u202f
\u205f\u3000\ufeff]/
空白字符,包括空格、
製表符、換頁符和換行符
\S /[^\s]/ 非空白字符
| /x|y/ x or y
\b word boundary 單詞邊界
\B none word boundary 非單詞邊界
^ /^abc/ 匹配以abc爲開始的字符串
$ /abc$/ 匹配以abc爲結束的字符串

字符類

咱們都知道用特定的正則表達式去匹配特定的字符串很輕鬆。由於不會出現其餘狀況,邏輯上講是很是清晰的事情。

舉個栗子:

let reg = /abc\b/
    // 以下圖 表示匹配 abc後面跟上單詞邊界。
    reg.test('abc bcd') // true
    reg.test('abcc') //false 由於abc後面緊跟單詞邊界

複製代碼

但是,有時候咱們的需求不是匹配特定字符,而是要匹配符合一些特性的字符。好比,須要匹配 a b c 任意一個,存在即知足條件。

簡而言之:我只要大家中的一個出現就OK。

這個時候,咱們就可使用元字符 [] 來構建這樣一個 字符類

這裏的類,咱們能夠聯想到編程語言的類,泛指一些符合特性的事物,而不是特指。

舉個例子:

let reg = /[abc]\b/
    // 以下圖 表示 one of abc 後面緊跟單詞邊界就知足條件
    reg.test('a') // true

複製代碼

字符類很強大,可是,若是咱們的需求是要匹配除了一個字符類以外的字符呢?

簡而言之:別人都行,就大家不能夠。

這個時候,咱們可使用字符類的反向類,使用元字符 ^ 在寫好的字符類裏面取反。

舉個例子:

let reg = /[^abc]\b/
    // 以下圖 表示 None of abc 後面緊跟單詞邊界就知足條件
    reg.test('e') // true

複製代碼

解釋一下單詞邊界的含義:單詞邊界這個概念,不少人都比較模糊。我只能說一下個人理解 在正則表達式中,\w 表明單詞字符,\W 表明非單詞字符,只要單詞字符緊挨着非單詞字符,那麼在這二者中間,就存在單詞邊界。

範圍類

字符類給咱們注入了一種全新的功能,相似於數據庫的模糊查詢。咱們能夠利用這一功能匹配範圍內的字符串了。 可是,應用場景多了,也會出現問題。

舉個例子:

//咱們想要匹配 數字1到8中的任意一個,咱們利用字符類的概念能夠這樣寫
    reg = /[12345678]/
    // 可能有的小夥伴能夠接受,那好,若是咱們想要匹配小寫字母,a 到 z 的任意一個字符
    reg = /[abcdefghijklmnopqrstuvwxyz]/ // 這樣一坨,寫的很難受

複製代碼

看上面的例子,寫代碼的難受,讀代碼的估計也不舒服。這時候,咱們須要範圍類幫忙。

所謂的範圍類,就是匹配具備必定規則的一段範圍以內的字符:

使用字符 -,來達成這一目標

舉個例子:

// 匹配 a 到 z 的任意一個字符
    reg = /[a-z]/
    // 匹配除了 a 到 z 的任意一個字符
    reg = /[^a-z]/

    // 常見的模式
    /\d/ 就至關於 /[0-9]/
    /\D/ 就至關於 /[^0-9]/
    /\w/ 就至關於 /[a-zA-Z0-9]/
    /\W/ 就至關於 /[^a-zA-Z0-9]/

複製代碼

一個問題:**-**不是元字符,是否能夠在範圍類中匹配?若是能夠,是否須要轉義或者其餘特殊操做。

匹配該字符在字符類中是能夠的,可是有注意點:即 - 只能夠放在範圍類的開頭或結尾,纔會匹配該字符。

不能夠出如今兩個字符中間,否則,該字符仍是會被看成範圍類中的特殊字符來對待

舉個例子:

let reg = /[1-z]/
    reg.test('-') // false
    reg.test('a') // true

    let reg = /[1-9-]/
    reg.test('-') // true
    reg.test('1') // true

複製代碼

量詞

咱們以前介紹的字符類或非字符類,只能匹配特定類出現一次,若是出現屢次,須要額外再寫相同的代碼進行匹配。

舉個例子:

// 需求:匹配有連續5個數字的字符串
    let reg = /\d\d\d\d\d/ // 那若是要匹配出現連續3至6個數字的字符串呢?
    let reg = /\d\d\d|\d\d\d\d|\d\d\d\d\d|\d\d\d\d\d\d/ // 這樣寫太複雜,我須要更簡單的寫法

複製代碼

有這樣的需求時,咱們就須要量詞來幫忙。

量詞有幾種表示的方式,各自表明不一樣的需求。

量詞的表示有如下這幾種表示:

+ 表示匹配一次或一次以上。one or more
? 表示匹配0次或一次。 one or less
* 表示匹配0次或0次以上。none or onemore
{m,n} 表示匹配 m 到 n 次。[m,n]閉區間 m less n most
{m,} 表示匹配至少 m 次。 m less
{m} 表示匹配出現 m 次

若是要表示至多出現 m 次,能夠這樣表示 {0,m}

重寫例子:

reg = /\d{5}/ // 匹配有5個連續數字的字符串
    reg = /\d{3,6}/ // 匹配有連續 3 至 6 個數字的字符串

複製代碼

你們能夠在圖形化工具裏面本身嘗試下,很直觀。

正則表達式的貪婪模式

注意:這裏,咱們會先應用 String.prototype.replace 方法來很形象的解釋正則表達式的貪婪模式。

來看一下這樣等應用場景:咱們須要匹配連續 5-10 個小寫字母等場景。目標字符串知足這個需求,可是匹配等結果是什麼?

是匹配到5個字母就不匹配仍是繼續匹配更多等字母直到匹配失敗呢?

人是貪婪的,因此人設計的正則表達式也是貪婪的。

在正則表達式中,會盡量的匹配更多的字符,直到匹配失敗爲止。

舉個例子:

reg = /[a-z]{5,10}/
    str = 'ahhsjkiosbsasdasllk' // str.length === 19
    咱們用 replace 方法來驗證一下,正則表達式匹配了多少字符
    str1 = str.replace(reg, 'Q') // str1.length === 10
    str1 = 'Qsasdasllk' 

複製代碼

上述的例子,咱們能夠看出,正則表達式是屬於貪婪模式。那麼咱們如今想要取消貪婪模式,能夠嗎?

能夠,只須要在量詞後加上**元字符?**就能夠取消貪婪模式啦。

舉個例子:

reg = /[a-z]{5,10}?/
    str = 'ahhsjkiosbsasdasllk' // str.length === 19
    咱們用 replace 方法來驗證一下,正則表達式匹配了多少字符
    str1 = str.replace(reg, 'Q') // str1.length === 15
    str1 = 'Qkiosbsasdasllk' 

複製代碼

分組

假如,咱們如今須要這樣一個需求,須要匹配包含'mistyyyy'連續重複2次的字符串。

這種狀況,咱們按照以前的寫法可能會這樣寫。

/mistyyyy{2}/

複製代碼

可是,這樣匹配是錯誤的,這表達的意思是y重複2次,而不是 mistyyyy 重複2次

這個時候,咱們可使用分組這個概念來幫助咱們。

用法:將須要分組的信息,用元字符()包含起來。這樣就可使量詞做用於分組了。

reg = /(mistyyyy){2}/
    str = 'Im mistyyyymistyyyymistyyyy yeah'
    reg.test(str) // true
    以下圖所示

複製代碼

再看一個例子,這時候,我要更名字了。mistyyyy 或者 missyyyy 都是能夠的。那咱們怎麼匹配它呢?

//咱們能夠這樣寫
    reg = /mistyyyy|missyyyy/
    可是利用分組,咱們能夠這樣寫
    reg = /mis(s|t)yyyy/
    reg.test('mistyyyy') // true
    reg.test('missyyyy') // true

複製代碼

捕獲和非捕獲分組

如今咱們來看一個,平時開發中常常出現的需求。如:將日期 '2018-12-23' 轉換爲 '23/12/2018'

這個時候,咱們就很頭大了。單純的匹配到這個日期並不困難。可是如何將其轉換這就成了難點。固然,咱們能夠進行最原始的方法進行解決。

reg = /\d{4}-\d{2}-\d{2}/
    '2018-12-23'.replace(reg, '23/12/2018')
    // 這樣的程序基本沒有靈活性。

複製代碼

這時候,咱們要講的捕獲就要出現了。前面講到了分組,既然能夠分組,那咱們也能夠捕獲分組。

捕獲分組又能夠稱爲引用:

  1. 一種是正向引用,用於正則表達式裏面的表示。
  2. 另外一種稱爲反向引用,經常使用於正則表達式匹配結果的替換。

咱們先看正向引用,舉個最適合的例子。

//咱們如今須要匹配一個 dom 節點,好比匹配 id 爲 container 的 div dom 節點。

    let domContainer = 
    `<div>
        <div id="container">
            this is container
        </div>
    </div>`
    reg = /<div id="container">([^<\/]+)<\/div>/m
    domContainer.replace(reg, 's') // <div>s</div>

複製代碼

這個時候,咱們可使用正向引用,即 \1 表明第一個分組的引用, \2 表明第二個分組的引用等等 以此類推

咱們來重寫正則表達式。

reg = /<(div) id="container">([^<\/]+)<\/\1>/m
    // 你們能夠試一下,一樣的效果。

複製代碼

介紹完正向引用,咱們來看一下反向引用。

在正則表達式進行分組時,當匹配結束時候,咱們但願能夠以分組爲單位進行字符串的替換,這樣可行嗎?

舉個例子

reg = /(\d{4})-(\d{2})-(\d{2})/
    // 這樣咱們就把正則寫好了。考慮到以前的例子,咱們須要將第三個分組放在開頭,第二個分組位置不變,第一個分組放在最後
    // 如何作
    '2018-12-23'.replace(reg, '$3/$2/$1') // "23/12/2018"

複製代碼

由上述例子能夠得知,反向引用就是用 '$1' 獲取第一個分組 '$2' 獲取第二個分組...以此類推

注意;是 '$1' 表明一個分組,而不是 $1,這裏須要注意一下

有時候,咱們根本不須要捕獲一個分組,就如剛纔 reg = /mis(s|t)yyyy/ 一種狀況。咱們只是想用分組實現一下 或 操做。 沒有分組的必要,其次,當正則表達式變得複雜起來,保持明顯的分組是頗有必要的。

這個時候,咱們能夠在分組的括號裏面加上 ?: 這樣就能夠取消捕獲了

reg = /mis(s|t)yyyy/
    'mistyyyy'.replace(reg, '$1') // 't'
    // 取消捕獲
    reg = /mis(?:s|t)yyyy/
    'mistyyyy'.replace(reg, '$1') // '$1'

複製代碼

咱們能夠看下圖片的比較,沒有分組了。說明取消了捕獲。

正向匹配和反向匹配

先解釋這兩個概念,咱們都知道在 JavaSCript 中,正則表達式匹配的順序是順着目標字符串進行匹配。

若是咱們須要設計一些帶條件的匹配規則,好比說:咱們須要匹配字符串 'mistyyy' 後面必須是 'good'

舉個例子:

reg = /mistyyyygood/    

複製代碼

這個時候,'mistyyyy' 後面是 'good' 可是此時,'good' 也被匹配到了,若是咱們用 replace 作替換,那麼 good 也會被替換掉。

要知足這樣的條件。咱們可使用正向匹配

規則以下:

  1. /expression(?=condition)/ 這表示expression後面必須緊跟 condition 做爲條件。
  2. /expression(?!condition)/ 這表示expression後面必須不是 condition 做爲條件。

舉個例子;

str = 'mistyyyygood boy'
    /mistyyyy(?=good)/.test(str) // true
    str.replace(/mistyyyy(?=good)/, 'you') // yougood boy

    /mistyyyy(?!good)/.test(str) // false

複製代碼

注意:此時,condition 只是做爲條件進行篩選,並不會被匹配到。

咱們能夠看一個例子

str = 'mistyyyygood boy'
    str.replace(/mistyyyy(?=good)/, '$1') // $1good boy

    // 咱們能夠看出條件是不會被匹配到到。

複製代碼

反向匹配,與正向匹配的規則相反,該特性是 ES 2018 新加的特性。

規則以下:

  1. /(?<=condition)expression/ 表示 expression 前必須知足 condition 條件才匹配
  2. /(?<!condition)expression/ 表示 expression 前必須不知足 condition 條件才匹配

詳解 regExp 對象屬性

咱們隨便寫一個正則,看一下打印出來的正則表達式的屬性有哪些

reg = /\u0002/yimgus
    {
        dotAll: true,
        flags: 'gimsuy',
        global: true,
        ignoreCase: true,
        lastIndex: 0,
        multiline: true,
        source: '\u0002',
        sticky: true,
        unicode: true,
        __proto__: Object
    }
    
複製代碼

dotAll,global,ignoreCase,multiline,stricky,unicode 分別表明修飾詞 s,g,i,m,y,u 是否出如今正在表達式的修飾詞位置。

flags 表示出現的修飾詞。

source 表示正則表達式的規則主體部分。

lastIndex 我的認爲最重要的屬性就是該屬性。下面會圍繞該屬性展開拓展一下。

咱們先看個奇怪的例子:

reg = /\d{2}/g
    str = '12sd'
    reg.lastIndex // 0
    reg.test(str) // true
    reg.lastindex // 2
    reg.test(str) // false
    reg.lastindex // 0

複製代碼

lastIndex的值類型是 number 類型。能夠進行讀寫操做。

舉個例子:

reg = /\d{2}/
    reg.lastindex // 0
    reg.lastIndex = 2
    reg.lastindex // 2

複製代碼

該屬性的含義是從目標字符串的 lastIndex 位置開始進行匹配。可是這是有限制的,只有當修飾符存在 g 或者 y 的時候,纔會起做用

舉個例子:

reg = /\d{2}/
    str = '12sd'
    reg.lastIndex = 2
    reg.test(str) // true
    reg.lastIndex // 2

    reg =/\d.\d/s
    str = '1\n2'
    reg.lastIndex = 2
    reg.test(str) // true
    reg.lastIndex // 2

    reg = /\d{2}/g
    str = '12sd'
    reg.lastIndex = 2
    reg.test(str) // false
    reg.lastIndex // 0

    reg = /\d{2}/y
    str = '12sd'
    reg.lastIndex = 2
    reg.test(str) // false
    reg.lastIndex // 0

複製代碼

經過以上的例子,咱們能夠看出,lastIndex 屬性隻影響了 修飾符 g 和 修飾符 y 的匹配結果。

也就是說:只有這兩種的形式是從目標字符串的 lastIndex 位置進行匹配的,其餘的修飾符會忽略這個屬性。

並且,這兩種修飾符匹配失敗了,lastIndex 會重置爲0。

因而可知,g 修飾符 和 修飾符 y 有相同的做用。那麼咱們來探尋一下他們的異同點。

相同點:

  1. 他們都會受 lastIndex 的屬性值影響正則開始匹配的效果。即影響從目標字符串的何處開始匹配
  2. 他們在匹配失敗後 lastindex 的屬性都會置爲 0。
  3. 他們匹配成功後,lastIndex 都會重置爲匹配成功的字符串的下一個字符。
  4. 修飾符 y 和 g 都不受元字符 ^ 從目標字符串開始的限制,它只受限於 lastIndex 的位置開始匹配。而且只從 lastIndex 的位置開始匹配。
reg1 = /\d/g
    reg2 = /\d/y
    str = '1ssss'
    reg1.lastIndex = reg2.lastIndex = 1
    reg1.test(str) // false
    reg2.test(str) // false
    reg1.lastindex // 0
    reg2.lastindex // 0
    // 說明都受 lastIndex 的影響,且匹配失敗都會置爲0

    reg1.test(str) // true
    reg2.test(str) // true
    reg1.lastIndex // 1
    reg2.lastIndex // 1
    // 匹配成功後,lastIndex 都會重置爲匹配成功的字符串(chharAt(0))的下一個字符

    reg1 = /^\d/g
    reg2 = /^\d/y
    str = '1ssss1'
    reg1.lastIndex = reg2.lastIndex = 1
    reg1.test(str) // false
    reg2.test(str) // false

複製代碼

不一樣點:

修飾符 g 是全局匹配,即匹配到目標字符串不會中止,繼續匹配下去,直到沒有找到全部符合規則的爲止。修飾符 y 不是全局匹配,找到符合規則的就會中止。

舉個例子:

reg1 = /\d/g
    reg2 = /\d/y
    str = '1sssss1'
    reg1.test(str) // true
    reg1.lastIndex // 1
    reg1.test(str) // true
    reg1.lastIndex // 7

    reg2.test(str) // true
    reg2.lastindex // 1
    reg2.test(str) // false
    reg2.lastindex // 0
    // 說明 修飾符 g 是全局匹配,而 y 不是全局匹配。

複製代碼

總結

關於正則表達式,咱們講了一些常見的語法和一些比較生澀的疑難點。對於正則表達式,咱們掌握了這些知識點,並不能徹底發揮其應有的實力。

咱們還應該掌握,應用正則的一些方法有:String 類型的 split,replace,match,search 。RegExp 類型的 test,exec 方法。

真正瞭解這些方法的應用,才能讓正則表達式的強大威力。這些方法,這裏暫時不講了,小夥伴們應該熟悉這些方法的使用,利用他們組合正則表達式,展現出強大的威力。

此外,正則表達式常見的應用場景有模版的解析,dom節點的提取分離,這裏面含有大量複雜的正則表達式。若是小夥伴須要精通掌握正則表達式,能夠閱讀sizzle這個庫,該庫是 JQ 的核心部分,專門處理複雜的 dom selector,但願咱們能夠繼續努力,將正則表達式真正的掌握。這樣,咱們就能夠寫出更加優雅,更加具備可讀性的代碼了。

相關文章
相關標籤/搜索