這周開始學習老姚大佬的《JavaScript 正則表達式迷你書》 , 而後習慣性的看完一遍後,整理一下知識點,便於之後本身從新複習。 git
我我的以爲:本身整理下來的資料,對於知識重現,效果不錯。 github
感謝原書做者老姚,本文無心抄襲,只是做爲本身知識點的整理,後續也會整理到本身的 JavaScript知識庫——《Cute-JavaScript》 網站中。 正則表達式
另外,請讀者們注意,這篇文章是知識點的整理,方便複習,因此不會介紹太詳細,由於畢竟原書寫得很是棒,剛入門的朋友,我仍是建議看下原書。 算法
而後能夠看看這篇文章,來回顧重要知識點。編程
原書這麼一句話,特別棒:正則表達式是匹配模式,要麼匹配字符,要麼匹配位置,要記住。數組
正則表達式的強大在於它的模糊匹配,這裏介紹兩個方向上的「模糊」:橫向模糊和縱向模糊。瀏覽器
即一個正則可匹配的字符串長度不固定,能夠是多種狀況。 微信
如 /ab{2,5}c/
表示匹配: 第一個字符是 "a"
,而後是 2 - 5 個字符 "b"
,最後是字符 "c"
:工具
let r = /ab{2,5}c/g; let s = "abc abbc abbbc abbbbbbc"; s.match(r); // ["abbc", "abbbc"]
即一個正則可匹配某個不肯定的字符,能夠有多種可能。 post
如 /[abc]/
表示匹配 "a", "b", "c"
中任意一個。
let r = /a[123]b/g; let s = "a0b a1b a4b"; s.match(r); // ["a1b"]
能夠指定字符範圍,好比 [1234abcdUVWXYZ]
就能夠表示成 [1-4a-dU-Z]
,使用 -
來進行縮寫。
若是要匹配 "a", "-", "z"
中任意一個字符,能夠這麼寫: [-az]
或 [a\-z]
或 [az-]
。
即須要排除某些字符時使用,經過在字符組第一個使用 ^
來表示取反,如 [^abc]
就表示匹配除了 "a", "b", "c"
的任意一個字符。
字符組 | 具體含義 |
---|---|
\d |
表示 [0-9] ,表示一位數字。 |
\D |
表示 [^0-9] ,表示除數字外的任意字符。 |
\w |
表示 [0-9a-zA-Z_] ,表示數字、大小寫字母和下劃線。 |
\W |
表示 [^0-9a-zA-Z_] ,表示非單詞字符。 |
\s |
表示 [\t\v\n\r\f] ,表示空白符,包含空格、水平製表符、垂直製表符、換行符、回車符、換頁符。 |
\S |
表示 [^\t\v\n\r\f] ,表示非空白字符。 |
. |
表示 [^\n\r\u2028\u2029] 。通配符,表示幾乎任意字符。換行符、回車符、行分隔符和段分隔符除外。 |
而後表示任意字符,就可使用 [\d\D]
、[\w\W]
、[\s\S]
和 [^]
任意一個。
量詞也稱重複,經常使用簡寫以下:
量詞 | 具體含義 |
---|---|
{m,} |
表示至少出現 m 次。 |
{m} |
等價於 {m, m} ,表示出現 m 次。 |
? |
等價於 {0, 1} ,表示出現或不出現。 |
+ |
等價於 {1, } ,表示至少出現1次。 |
* |
等價於 {0, } ,表示出現任意次,也有可能不出現。 |
在正則 /\d{2,4}/
,表示數字連續出現 2 - 4 次,能夠匹配到 2 位、 3 位、4 位連續數字。
可是在 貪婪匹配 如 /\d{2,4}/g
,會儘量多匹配,如超過 4 個,就只匹配 4 個,若有 3 個,就匹配 3 位。
而在 惰性匹配 如 /\d{2,4}?/g
,會 儘量少 匹配,如超過 2 個,就只匹配 2 個,不會繼續匹配下去。
let r1 = /\d{2,4}/g; let r2 = /\d{2,4}?/g; let s = "123 1234 12345"; s.match(r1); // ["123", "1234", "1234"] s.match(r2); // ["12", "12", "34", "12", "34"]
惰性量詞 | 貪婪量詞 |
---|---|
{m,m}? |
{m,m} |
{m,}? |
{m,} |
?? |
? |
+? |
+ |
*? |
* |
即提供多個子匹配模式任選一個,使用 |
(管道符)分隔,因爲分支結構也是惰性,即匹配上一個後,就不會繼續匹配後續的。
格式如:(r1|r2|r3)
,咱們就可使用 /leo|pingan/
來匹配 "leo"
和 "pingan"
。
let r = /leo|pingan/g; let s = "leo cool,pingan good."; s.match(r);// ["leo", "pingan"] // 多選分支的惰性表現 let r1 = /leo|leooo/g; let r2 = /leooo|leo/g; let s = "leooo"; s.match(r1);// ["leo"] s.match(r2);// ["leooo"]
匹配字符,無非就是字符組、量詞和分支結構的組合使用。
let r = /#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}/g; let s = "#ffaacc #Ff00DD #fFF #01d #9Gd"; s.match(r); // ["#ffaacc", "#Ff00DD", "#fFF", "#01d"]
// 時間 12:23 或 01:09 let r = /^([01][0-9]|[2][0-3]):[0-5][0-9]$/; r.test("23:25"); // true r.test("03:05"); // true // 時間 12:23 或 1:9 let r = /^(0?[0-9]|1[0-9]|[2][0-3]):(0?[0-9]|[1-5][0-9])$/; r.test("23:25"); // true r.test("03:05"); // true r.test("3:5"); // true // 日期 yyyy-mm-dd let r = /^[0-9]{4}-(0[1-9]|[1][0-2])-(0[1-9]|[12][0-9]|3[01])$/; r.test("2019-09-19"); // true r.test("2019-09-32"); // false
盤符使用 [a-zA-Z]:\\
,這裏須要注意 \
字符須要轉義,而且盤符不區分大小寫;
文件名或文件夾名,不能包含特殊字符,使用 [^\\:*<>|"?\r\n/]
表示合法字符;
而且至少有一個字符,還有能夠出現任意次,就可使用 ([^\\:*<>|"?\r\n/]+\\)*
匹配任意個 文件夾\
;
還有路徑最後一部分能夠是 文件夾
,即沒有 \
因而表示成 ([^\\:*<>|"?\r\n/]+)?
。
let r = /^[a-zA-Z]:\\([^\\:*<>|"?\r\n/]+\\)*([^\\:*<>|"?\r\n/]+)?$/; r.test("C:\\document\\leo\\a.png"); // true r.test("C:\\document\\leo\\"); // true r.test("C:\\document"); // true r.test("C:\\"); // true
如提取 <div id="leo" class="good"></id>
中的 id="leo"
:
let r1 = /id=".*"/; // tips1 let r2 = /id=".*?"/; // tips2 let r3 = /id="[^"]*"/; // tips3 let s = '<div id="leo" class="good"></id>'; s.match(r1)[0]; // id="leo" class="good" s.match(r2)[0]; // id="leo" s.match(r3)[0]; // id="leo"
tips1:因爲 .
匹配雙引號,且 *
貪婪,就會持續匹配到最後一個雙引號結束。
tips2:使用惰性匹配,但效率低,有回溯問題。
tips3:最終優化。
位置匹配,就是要匹配每一個字符兩邊的位置。
在 ES5
中有6個位置: ^
,$
,\b
,\B
,(?=p)
和 (?!p)
。
另外把位置理解成空字符是很是有用的:
/^^hello$$/.test('hello'); // true /^^^hello$$/.test('hello'); // true
^
匹配開頭,多行中匹配行開頭。 $
匹配結尾,多行中匹配行結尾。
"hello".replace(/^|$/g, "#"); // "#hello#" "hello\nleo\nhaha".replace(/^|$/gm, "#"); /* #hello# #leo# #haha# */
多行匹配模式使用 m
修飾符。
\b
和 \B
\b
匹配單詞邊界,即 \w
和 \W
之間的位置,包括 \w
和 ^
之間的位置,和 \w
和 $
之間的位置。 \B
和 \b
相反,即非單詞邊界,匹配中除去 \b
,剩下的都是 \B
的。
也就是 \w
與 \w
、 \W
與 \W
、^
與 \W
,\W
與 $
之間的位置。。
"[HI] Leo_1.mp4".replace(/\b/g,"#"); // "[#HI#] #Leo_1#.#mp4#" "[HI] Leo_1.mp4".replace(/\B/g,"#"); // "#[H#I]# L#e#o#_#1.m#p#4"
(?=p)
和 (?!p)
p
爲一個子模式,即 (?=p)
匹配前面是 p
的位置,而 (?!p)
則匹配前面不是 p
的位置。
"hello".replace(/(?=l)/g, "#"); // "he#l#lo" "hello".replace(/(?!l)/g, "#"); // "#h#ell#o#"
// 匹配最後一個逗號 "12345678".replace(/(?=\d{3}$)/g, ","); // "12345,678" // 匹配全部逗號 "12345678".replace(/(?=(\d{3})+$)/g, ","); // "12,345,678" // 匹配其他 "123456789".replace(/(?=(\d{3})+$)/g, ","); // ",123,456,789" // 修改 "123456789".replace(/(?!^)(?=(\d{3})+$)/g, ","); // "12,345,678" // 其餘形式 "12345678 123456789".replace(/(?!\b)(?=(\d{3})+\b)/g, ","); // (?!\b) 等於 \B ,要求當前是一個位置,但不是 \b 前面的位置 // "12,345,678 123,456,789"
let num = 1888; num.toFixed(2).replace(/\B(?=(\d{3})+\b)/g, ",").replace(/^/,"$$ "); // "$ 1,888.00"
// 密碼長度 6-12 位數字或字母 let r = /^[0-9A-Za-z]{6,12}$/; // 必須包含一個字符(數字) + 密碼長度 6-12 位數字或字母 let r = /(?=.*[0-9])^[0-9A-Za-z]{6,12}$/; // 必須包含兩個個字符(數字和小寫字符) + 密碼長度 6-12 位數字或字母 let r = /(?=.*[0-9])(?=.*[a-z])^[0-9A-Za-z]{6,12}$/; r.test("aa1234566"); // true r.test("1234566"); // false // 密碼長度 6-12 位數字或字母 // 即 不能全是數字 或 不能全是大寫或小寫字母 let r = /(?!^[0-9]{6,12}$)(?!^[a-z]{6,12}$)(?!^[A-Z]{6,12}$)^[0-9A-Za-z]{6,12}$/;
簡單理解:括號提供了分組,便於咱們使用它。
一般有兩種引用狀況:在JS代碼中引入,和在正則表達式中引入。
分組和分支結構,主要是強調括號內是一個總體,即提供子表達式。
/(ab)+/g
匹配連續出現的 ab
。/(a|b)+/g
匹配出現的 a
或 b
表達式。如在日期匹配的時候,就能夠這麼改造:
// 原來 let r = /\d{4}-\d{2}-\d{2}/; // 如今 let r = /(\d{4})-(\d{2})-(\d{2})/;
"2019-03-14".match(r); r.exec("2019-03-14"); // ["2019-03-14", "2019", "03", "14", index: 0, input: "2019-03-14"] RegExp.$1; // "2019" RegExp.$2; // "03" RegExp.$3; // "14"
將 yyyy-mm-dd
轉成 mm/dd/yyyy
。
"2019-03-14".replace(r, "$2/$3/$1"); // 等價於 "2019-03-14".replace(r, function(){ return RegExp.$2 + '/' + RegExp.$3 + '/' + RegExp.$1; });
使用 \n
表示第 n
個分組,好比 \1
表示第 1
個分組:
let r = /\d{4}(-|\/|\.)\d{2}\1\d{2}/; r.test("2019-03-15"); r.test("2019/03/15"); r.test("2019.03.15"); r.test("2019-03/15");
按照開括號的順序:
let r = /^((\d)(\d(\d)))\1\2\3\4$/; let s = "1231231233"; r.test(s); console.log([RegExp.$1,RegExp.$2,RegExp.$3,RegExp.$4]); // ["123", "1", "23", "3"]
\10
表示的是第 10 個分組,若要匹配 \
和 0
時,使用 (?:\1)0
或 \1(?:0)
。
let r = /(1)(2)(3)(4)(5)(6)(7)(8)(9)(#) \10+/; let s = "123456789# #####"; r.test(s); // true
如匹配 \2
是前面不存在,則匹配 \2
自己,即對 2
的轉義,不一樣瀏覽器可能不一樣:
let r = /\1\2\3\4/; r.test("\1\2\3\4"); // true "\1\2\3\4".split('');// ["", "", "", ""]
當分組後面有量詞的話,則捕獲的是最後一次的匹配:
"12345".match(/(\d)+/); // ["12345", "5", index: 0, input: "12345"] /(\d)+ \1/.test("12345 1"); // false /(\d)+ \1/.test("12345 5"); // true
這裏只寫出核心代碼。
trim
方法// 1 匹配首尾空白符,替換成空字符 " aaa ".replace(/^\s+|\s+$/g, ""); // "aaa" // 2 匹配整個字符串,再用引用提取對應數據 " aaa ".replace(/^\s*(.*?)\s*$/g, "$1");// "aaa"
"hi leo hi boy!".toLowerCase().replace( /(?:^|\s)\w/g, c => c.toUpperCase() ); // "Hi Leo Hi Boy!"
"-leo-and-pingan".replace(/[-_\s]+(.)?/g, (match, c) => c ? c.toUpperCase() : '' ); // "LeoAndPingan" "LeoAndPingan".replace(/([A-Z])/g, "-$1").replace( /[-_\s]+g/,"-" ).toLowerCase(); // "-leo-and-pingan"
匹配成對標籤 <h1>leo<\h1>
,而不匹配不成對標籤 <h1>leo<\h2>
。
let r = /<([^>]+)>[\d\D]*<\/\1>/; r.test("<h1>leo leo leo</h1>"); // true r.test("<a>leo leo leo</a>"); // true r.test("<h1>leo leo leo</h2>"); // false
概念理解起來比較容易。
好比用 /ab{1,3}c/
去匹配下面兩個字符串。
abbbc
,按順序匹配,到了第 3 個 b
後,直接匹配 c
,這樣就沒有回溯。abbc
,按順序匹配,到了第 2 個 b
後,因爲規則是 b{1,3}
,則會繼續往下匹配,而後發現下一位是 c
,因而回退到前一個位置,從新匹配,這就是回溯。另外像 /".*"/
來匹配 "abc"de
的話,就會有三個回溯狀況,爲了減小沒必要要的回溯,咱們能夠把正則修改成 /"[^"]*"/
。
介紹
回溯法,也稱試探法,本質上是深度優先探索算法,基本思路是:匹配過程當中後退到以前某一步從新探索的過程。
多個貪婪量詞挨着存在,並相互衝突時,會看匹配順序,深度優先搜索:
"12345".match(/(\d{1,3})(\d{1,3})/); // ["12345", "123", "45", index: 0, input: "12345"]
有時候會由於回溯,致使實際惰性量詞匹配到的不是最少的數量:
"12345".match(/(\d{1,3}?)(\d{1,3})/); // 沒有回溯的狀況 ["1234", "1", "234", index: 0, input: "12345"] "12345".match(/^\d{1,3}?\d{1,3}$/); // 有回溯的狀況 ["12345", index: 0, input: "12345"]
分支機構,若是一個分支總體不匹配,會繼續嘗試剩下分支,也能夠當作一種回溯。
"candy".match(/can|candy/); // ["can", index: 0, input: "candy"] "candy".match(/^(?:can|candy)$/); // ["candy", index: 0, input: "candy"]
簡單總結:一個個嘗試,直到,要麼後退某一步總體匹配成功,要麼最後試完發現總體不匹配。
拆分正則代碼塊,是理解正則的關鍵。
在 JavaScrip 正則表達式有如下結構:
a
匹配字符 a
。[0-9]
匹配任意一個數字。a{1,3}
匹配連續最多出現 3 次的a
字符。^
匹配字符串的開頭。(ab)
匹配 ab
兩個字符連續出現。ab|bc
匹配 ab
或 bc
字符。另外還有如下操做符:
優先級 | 操做符描述 | 操做符 | |
---|---|---|---|
1 | 轉義符 | \ |
|
2 | 括號和方括號 | (...) /(?:...) /(?=...) /(?!...) /[...] |
|
3 | 量詞限定符 | {m} /{m,n} /{m,} /? /* /+ |
|
4 | 位置和序列 | ^ /$ /\元字符 /通常字符 |
|
5 | 管道符 | ` | ` |
Tips:優先級從上到下,由高到低。
不能寫成 /^abc|bcd$/
,而是要寫成 /^(abc|bcd)$/
。
須要匹配:每一個字符是 a
/b
/c
中其中一個,而且字符串長度是 3 的倍數:
不能寫成 /^[abc]{3}+$/
,而是要寫成 /([abc]{3})+/
。
元字符就是正則中的特殊字符,當匹配元字符就須要轉義,如:
^
、$
、.
、*
、+
、?
、|
、\
、/
、(
、)
、[
、]
、{
、}
、=
、!
、:
、-
。
// "[abc]" => /\[abc\]/ 或者 /\[abc]/ // "{1,3}" => /\{1\}/ 或者 /\{1}/ 由於不構成字符組
/^(\d{15}|\d{17})[\dxX]$/.test("390999199999999999");// true
須要好好分析:
let r = /^((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])$/
正則的構建須要考慮如下幾點的平衡:
咱們還須要考慮這麼幾個問題:
如能使用其餘 API 簡單快速解決問題就不須要使用正則:
"2019-03-16".match(/^(\d{4})-(\d{2})-(\d{2})/); // 間接獲取 ["2019", "03", "16"] "2019-03-16".split("-"); // ["2019", "03", "16"] "?id=leo".search(/\?/); // 0 "?id=leo".indexOf("?"); // 0 "JavaScript".match(/.{4}(.+)/)[1]; // "Script" "JavaScript".substring(4); // "Script"
/(?!^[0-9]{6,12}$)(?!^[a-z]{6,12}$)(?!^[A-Z]{6,12}$)^[0-9A-Za-z]{6,12}$/
將這個正則拆分紅多個小塊,以下:
var regex1 = /^[0-9A-Za-z]{6,12}$/; var regex2 = /^[0-9]{6,12}$/; var regex3 = /^[A-Z]{6,12}$/; var regex4 = /^[a-z]{6,12}$/; function checkPassword (string) { if (!regex1.test(string)) return false; if (regex2.test(string)) return false; if (regex3.test(string)) return false; if (regex4.test(string)) return false; return true; }
即須要匹配到預期目標,且不匹配非預期的目標。
如須要匹配下面固定電話號碼,能夠分別寫出對應正則:
055188888888 => /^0\d{2,3}[1-9]\d{6,7}$/ 0551-88888888 => /^0\d{2,3}-[1-9]\d{6,7}$/ (0551)88888888 => /^0\d{2,3}-[1-9]\d{6,7}$/
而後合併:
let r = /^0\d{2,3}[1-9]\d{6,7}$|^0\d{2,3}-[1-9]\d{6,7}$|^\(0\d{2,3}\)[1-9]\d{6,7}$/
而後提取公共部分:
let r = /^(0\d{2,3}|0\d{2,3}-|\(0\d{2,3}\))[1-9]\d{6,7}$/
再優化:
let r = /^(0\d{2,3}-?|\(0\d{2,3}\))[1-9]\d{6,7}$/
先肯定,符號部分([+-]
)、整數部分(\d+
)和小數部分(\.\d+
)。
1.2三、+1.2三、-1.23 => /^[+-]?\d+\.\d+$/ 十、+十、-10 => /^[+-]?\d+$/ .二、+.二、-.2 => /^[+-]?\.\d+$/
整理後:
let r = /^[+-]?(\d+\.\d+|\d+|\.\d+)$/; // 考慮不匹配 +.2 或 -.2 let r = /^([+-])?(\d+\.\d+|\d+|\.\d+)$/; // 考慮不匹配 012 這類 0 開頭的整數 let r = /^[+-]?(\d+)?(\.)?\d+$/;
正則表達式運行過程:
咱們經常優化對 3 和 4
步進行優化:
如 /"[^"]*"/
代替 /".*?"/
。
當不須要使用分組引用和反向引用時,此時可使用非捕獲分組。
如 /^[-]?(?:\d\.\d+|\d+|\.\d+)$/
代替 /^[-]?(\d\.\d+|\d+|\.\d+)$/
。
加快判斷是否匹配失敗,進而加快移位的速度。
如 /aa*/
代替 /a+/
。
減小匹配過程當中可消除的重複。
如 /^(?:abc|def)/
代替 /^abc|^def/
。
如 /rea?d/
代替 /red|read/
。
這裏要掌握正則表達式怎麼用,一般會有這麼四個操做:
匹配本質上是查找,咱們能夠藉助相關API操做:
// 檢查字符串是否包含數字 let r = /\d/, s = "abc123"; !!s.search(r); // true r.test(s); // true !!s.match(r); // true !!r.exec(s); // true
"leo,pingan".split(/,/); // ["leo", "pingan"] let r = /\D/, s = "2019-03-16"; s.split(r); // ["2019", "03", "16"] s.split(r); // ["2019", "03", "16"] s.split(r); // ["2019", "03", "16"]
// 提取日期年月日 let r = /^(\d{4})\D(\d{2})\D(\d{2})$/; let s = "2019-03-16"; s.match(r); // ["2019-03-16", "2019", "03", "16", index: 0, input: "2019-03-16"] r.exec(s); // ["2019-03-16", "2019", "03", "16", index: 0, input: "2019-03-16"] r.test(s); // RegExp.$1 => "2019" RegExp.$2 => "03" RegExp.$3 => "16" s.search(r);// RegExp.$1 => "2019" RegExp.$2 => "03" RegExp.$3 => "16"
// yyyy-mm-dd 替換成 yyyy/mm/dd "2019-03-16".replace(/-/g, "/");
search
和 match
參數問題這兩個方法會把字符串轉換成正則,因此要加轉義
let s = "2019.03.16"; s.search('.'); // 0 s.search('\\.'); // 4 s.search(/\./); // 4 s.match('.'); // ["2", index: 0, input: "2019.03.16"] s.match('\\.'); // [".", index: 4, input: "2019.03.16"] s.match(/\./); // [".", index: 4, input: "2019.03.16"] // 其餘不用轉義 s.split('.'); s.replace('.', '/');
match
返回結果的格式問題match
參數有 g
會返回全部匹配的內容,沒有 g
則返回標準匹配格式:
let s = "2019.03.16"; s.match(/\b(\d+)\b/); // ["2019", "2019", index: 0, input: "2019.03.16"] s.match(/\b(\d+)\b/g); // ["2019", "03", "16"]
test
總體匹配時須要使用 ^
和 $
/123/.test("a123b"); // true /^123$/.test("a123b"); // false /^123$/.test("123"); // true
split
的注意點split
第二個參數是 結果數組的最大長度:
"leo,pingan,pingan8787".split(/,/, 2); // ["leo", "pingan"]
使用正則分組,會包含分隔符:
"leo,pingan,pingan8787".split(/(,)/); // ["leo", ",", "pingan", ",", "pingan8787"]
修飾符 | 描述 |
---|---|
g |
全局匹配,即找到全部匹配的,單詞是 global 。 |
i |
忽略字母大小寫,單詞是 ingoreCase 。 |
m |
多行匹配,隻影響 ^ 和 $ ,兩者變成行的概念,即行開頭和行結尾。單詞是 multiline 。 |
文章到這結束,感謝閱讀,也感謝老姚大佬的這本書
Author | 王平安 |
---|---|
pingan8787@qq.com | |
博 客 | www.pingan8787.com |
微 信 | pingan8787 |
每日文章推薦 | https://github.com/pingan8787... |
ES小冊 | js.pingan8787.com |