W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗(yàn)值獎勵
模式的一部分可以用括號括起來 ?(...)
?。這被稱為“捕獲組(capturing group)”。
這有兩個影響:
讓我們看看在示例中的括號是如何工作的。
不帶括號,模式 go+
表示 g
字符,其后 o
重復(fù)一次或多次。例如 goooo
或 gooooooooo
。
括號將字符組合,所以 (go)+
匹配 go
,gogo
,gogogo
等。
alert( 'Gogogo now!'.match(/(go)+/i) ); // "Gogogo"
讓我們做些更復(fù)雜的事 —— 搜索域名的正則表達(dá)式。
例如:
mail.com
users.mail.com
smith.users.mail.com
正如我們所看到的,一個域名由重復(fù)的單詞組成,每個單詞后面有一個點(diǎn),除了最后一個單詞。
在正則表達(dá)式中是 (\w+\.)+\w+
:
let regexp = /(\w+\.)+\w+/g;
alert( "site.com my.site.com".match(regexp) ); // site.com,my.site.com
搜索有效,但該模式無法匹配帶有連字符的域名,例如 my-site.com,因?yàn)檫B字符不屬于 \w
類。
我們可以通過用 [\w-]
替換 \w
來匹配除最后一個單詞以外的每個單詞:([\w-]+\.)+\w+
。
擴(kuò)展一下上面這個示例。我們可以基于它為電子郵件創(chuàng)建一個正則表達(dá)式。
電子郵件的格式為:name@domain
。名稱可以是任何單詞,允許使用連字符和點(diǎn)。在正則表達(dá)式中為 [-.\w]+
。
模式:
let regexp = /[-.\w]+@([\w-]+\.)+[\w-]+/g;
alert("my@mail.com @ his@site.com.uk".match(regexp)); // my@mail.com, his@site.com.uk
該正則表達(dá)式并不完美的,但多數(shù)情況下都能正確匹配,并且有助于修復(fù)輸入郵箱時的意外錯誤輸入。唯一真正可靠的電子郵件檢查只能通過發(fā)送電子郵件來完成。
括號被從左到右編號。正則引擎會記住它們各自匹配的內(nèi)容,并允許在結(jié)果中獲取它。
方法 str.match(regexp)
,如果 regexp
沒有修飾符 g
,將查找第一個匹配項,并將它作為數(shù)組返回:
0
處:完整的匹配項。1
處:第一個括號的內(nèi)容。2
處:第二個括號的內(nèi)容。例如,我們想找到 HTML 標(biāo)簽 <.*?>
并處理它們。將標(biāo)簽內(nèi)容(尖括號內(nèi)的內(nèi)容)放在單獨(dú)的變量中會很方便。
讓我們將內(nèi)部內(nèi)容包裝在括號中,像這樣:<(.*?)>
。
現(xiàn)在,我們在結(jié)果數(shù)組中得到了標(biāo)簽的整體 <h1>
及其內(nèi)容 h1
:
let str = '<h1>Hello, world!</h1>';
let tag = str.match(/<(.*?)>/);
alert( tag[0] ); // <h1>
alert( tag[1] ); // h1
括號可以嵌套。在這種情況下,編號也從左到右。
例如,在搜索標(biāo)簽 <span class="my">
時,我們可能會對以下內(nèi)容感興趣:
span class="my"
。span
。class="my"
。讓我們?yōu)樗鼈兲砑永ㄌ枺?code><(([a-z]+)\s*([^>]*))>。
這是它們的編號方式(根據(jù)左括號從左到右):
驗(yàn)證:
let str = '<span class="my">';
let regexp = /<(([a-z]+)\s*([^>]*))>/;
let result = str.match(regexp);
alert(result[0]); // <span class="my">
alert(result[1]); // span class="my"
alert(result[2]); // span
alert(result[3]); // class="my"
result
的索引 0 中始終保存的是正則表達(dá)式的完整匹配項。
然后是按左括號從左到右編號的組。第一組返回為 result[1]
。它包含了整個標(biāo)簽內(nèi)容。
然后是 result[2]
,從第二個左括號開始分組 ([a-z]+)
—— 標(biāo)簽名稱,然后在 result[3]
中:([^>]*)
。
字符串中每個組的內(nèi)容:
即使組是可選的并且在匹配項中不存在(例如,具有量詞 (...)?
),也存在相應(yīng)的 result
數(shù)組項,并且等于 undefined
。
例如,讓我們考慮正則表達(dá)式 a(z)?(c)?
。它查找 "a"
,后面是可選的 "z"
,然后是可選的 "c"
。
如果我們在單個字母的字符串上運(yùn)行 a
,則結(jié)果為:
let match = 'a'.match(/a(z)?(c)?/);
alert( match.length ); // 3
alert( match[0] ); // a(完整的匹配項)
alert( match[1] ); // undefined
alert( match[2] ); // undefined
數(shù)組的長度為 3
,但所有組均為空。
對字符串 ac
的匹配會更復(fù)雜:
let match = 'ac'.match(/a(z)?(c)?/)
alert( match.length ); // 3
alert( match[0] ); // ac(完整的匹配項)
alert( match[1] ); // undefined, 因?yàn)闆]有 (z)? 的匹配項
alert( match[2] ); // c
數(shù)組長度依然是:3
。但沒有組 (z)?
的匹配項,所以結(jié)果是 ["ac", undefined, "c"]
。
?
matchAll
? 是一個新方法,可能需要使用 polyfill舊的瀏覽器不支持
matchAll
。
可能需要進(jìn)行 polyfill,例如 https://github.com/ljharb/String.prototype.matchAll.
當(dāng)我們搜索所有匹配項(修飾符 g
)時,match
方法不會返回組的內(nèi)容。
例如,讓我們查找字符串中的所有標(biāo)簽:
let str = '<h1> <h2>';
let tags = str.match(/<(.*?)>/g);
alert( tags ); // <h1>,<h2>
結(jié)果是一個匹配數(shù)組,但沒有每個匹配項的詳細(xì)信息。但是實(shí)際上,我們通常需要在結(jié)果中獲取捕獲組的內(nèi)容。
要獲取它們,我們應(yīng)該使用方法 str.matchAll(regexp)
進(jìn)行搜索。
在使用 match
很長一段時間后,它才被作為“新的改進(jìn)版本”被加入到 JavaScript 中。
就像 match
一樣,它尋找匹配項,但有 3 個區(qū)別:
g
? 時,它將每個匹配項以包含組的數(shù)組的形式返回。null
?,而是一個空的可迭代對象。例如:
let results = '<h1> <h2>'.matchAll(/<(.*?)>/gi);
// results —— 不是數(shù)組,而是一個迭代對象
alert(results); // [object RegExp String Iterator]
alert(results[0]); // undefined (*)
results = Array.from(results); // 讓我們將其轉(zhuǎn)換為數(shù)組
alert(results[0]); // <h1>,h1(第一個標(biāo)簽)
alert(results[1]); // <h2>,h2(第二個標(biāo)簽)
我們可以看到,第一個區(qū)別非常重要,如 (*)
行所示。我們無法獲得 results[0]
的匹配項,因?yàn)樵搶ο蟛⒉皇莻螖?shù)組。我們可以使用 Array.from
把它變成一個真正的 Array
。在 Iterable object(可迭代對象) 一文中有關(guān)于偽數(shù)組和可迭代對象的更多詳細(xì)內(nèi)容。
如果我們只需要遍歷結(jié)果,則 Array.from
沒有必要:
let results = '<h1> <h2>'.matchAll(/<(.*?)>/gi);
for(let result of results) {
alert(result);
// 第一個 alert:<h1>,h1
// 第二個:<h2>,h2
}
……或使用解構(gòu):
let [tag1, tag2] = '<h1> <h2>'.matchAll(/<(.*?)>/gi);
matchAll
返回的每個匹配項,與不帶修飾符 g
的 match
所返回的格式相同:具有額外 index
(字符串中的匹配索引)屬性和 input
(源字符串)的數(shù)組:
let results = '<h1> <h2>'.matchAll(/<(.*?)>/gi);
let [tag1, tag2] = results;
alert( tag1[0] ); // <h1>
alert( tag1[1] ); // h1
alert( tag1.index ); // 0
alert( tag1.input ); // <h1> <h2>
為什么 ?
matchAll
? 的結(jié)果是可迭代對象而不是數(shù)組?為什么這個方法這樣設(shè)計?原因很簡單 —— 為了優(yōu)化。
調(diào)用
matchAll
不會執(zhí)行搜索。相反,它返回一個可迭代對象,最初沒有結(jié)果。每次我們迭代它時才會執(zhí)行搜索,例如在循環(huán)中。
因此,這將根據(jù)需要找出盡可能多的結(jié)果,而不是全部。
例如,文本中可能有 100 個匹配項,但在一個
for..of
循環(huán)中,我們找到了 5 個匹配項,然后覺得足夠了并做出一個break
。這時引擎就不會花時間查找其他 95 個匹配。
用數(shù)字記錄組很困難。對于簡單的模式,它是可行的,但對于更復(fù)雜的模式,計算括號很不方便。我們有一個更好的選擇:給括號命名。
在左括號后緊跟著放置 ?<name>
即可完成對括號的命名。
例如,讓我們查找 “year-month-day” 格式的日期:
let dateRegexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/;
let str = "2019-04-30";
let groups = str.match(dateRegexp).groups;
alert(groups.year); // 2019
alert(groups.month); // 04
alert(groups.day); // 30
正如你所看到的,匹配的組在 .groups
屬性中。
要查找所有日期,我們可以添加修飾符 g
。
我們還需要 matchAll
以獲取完整的組匹配:
let dateRegexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/g;
let str = "2019-10-30 2020-01-01";
let results = str.matchAll(dateRegexp);
for(let result of results) {
let {year, month, day} = result.groups;
alert(`${day}.${month}.${year}`);
// 第一個 alert:30.10.2019
// 第二個:01.01.2020
}
讓我們能夠替換 str
中 regexp
的所有匹配項的方法 str.replace(regexp, replacement)
允許我們在 replacement
字符串中使用括號中的內(nèi)容。這使用 $n
來完成,其中 n
是組號。
例如,
let str = "John Bull";
let regexp = /(\w+) (\w+)/;
alert( str.replace(regexp, '$2, $1') ); // Bull, John
對于命名的括號,引用為 $<name>
。
例如,讓我們將日期格式從 “year-month-day” 更改為 “day.month.year”:
let regexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/g;
let str = "2019-10-30, 2020-01-01";
alert( str.replace(regexp, '{#content}lt;day>.{#content}lt;month>.{#content}lt;year>') );
// 30.10.2019, 01.01.2020
有時我們需要用括號才能正確應(yīng)用量詞,但我們不希望它們的內(nèi)容出現(xiàn)在結(jié)果中。
可以通過在開頭添加 ?:
來排除組。
例如,如果我們要查找 (go)+
,但不希望括號內(nèi)容(go
)作為一個單獨(dú)的數(shù)組項,則可以編寫:(?:go)+
。
在下面的示例中,我們僅將名稱 John
作為匹配項的單獨(dú)成員:
let str = "Gogogo John!";
// ?: 從捕獲組中排除 'go'
let regexp = /(?:go)+ (\w+)/i;
let result = str.match(regexp);
alert( result[0] ); // Gogogo John(完整的匹配項)
alert( result[1] ); // John
alert( result.length ); // 2(在數(shù)組中沒有其他數(shù)組項)
括號將正則表達(dá)式中的一部分組合在一起,以便量詞可以整體應(yīng)用。
括號組從左到右編號,可以選擇用 (?<name>...)
命名。
可以在結(jié)果中獲得按組匹配的內(nèi)容:
str.match
? 僅當(dāng)不帶修飾符 ?g
? 時返回捕獲組。str.matchAll
? 始終返回捕獲組。如果括號沒有名稱,則匹配數(shù)組按編號提供其內(nèi)容。命名括號還可使用屬性 groups
。
我們還可以在 str.replace
的替換字符串中使用括號內(nèi)容:通過數(shù)字 $n
或者名稱 $<name>
。
可以通過在組的開頭添加 ?:
來排除編號。當(dāng)我們需要對整個組應(yīng)用量詞,但不希望將其作為結(jié)果數(shù)組中的單獨(dú)項時這很有用。我們也不能在替換字符串中引用這樣的括號。
網(wǎng)絡(luò)接口的 MAC 地址 由 6 個以冒號分隔的兩位十六進(jìn)制數(shù)字組成。
例如:'01:32:54:67:89:AB'
。
編寫一個檢查字符串是否為 MAC 地址的正則表達(dá)式。
用例:
let regexp = /你的正則表達(dá)式/;
alert( regexp.test('01:32:54:67:89:AB') ); // true
alert( regexp.test('0132546789AB') ); // false (沒有冒號分隔)
alert( regexp.test('01:32:54:67:89') ); // false (5 個數(shù)字,必須為 6 個)
alert( regexp.test('01:32:54:67:89:ZZ') ) // false (尾部為 ZZ)
一個兩位的十六進(jìn)制數(shù)可以用 [0-9a-f]{2}
(假設(shè)已設(shè)定修飾符 i
)進(jìn)行匹配。
我們需要匹配數(shù)字 NN
,然后再重復(fù) 5 次 :NN
(匹配更多數(shù)字);
所以正則表達(dá)式為:[0-9a-f]{2}(:[0-9a-f]{2}){5}
現(xiàn)在讓我們驗(yàn)證一下此匹配規(guī)則可以捕獲整個文本:從開頭開始,在結(jié)尾結(jié)束。這是通過將模式包裝在 ^...$
中實(shí)現(xiàn)的。
最終:
let regexp = /^[0-9a-f]{2}(:[0-9a-f]{2}){5}$/i;
alert( regexp.test('01:32:54:67:89:AB') ); // true
alert( regexp.test('0132546789AB') ); // false (沒有分號分隔)
alert( regexp.test('01:32:54:67:89') ); // false (5 個數(shù)字,必須為 6 個)
alert( regexp.test('01:32:54:67:89:ZZ') ) // false (尾部為 ZZ)
編寫一個匹配 #abc
或 #abcdef
格式的顏色值的正則表達(dá)式。即:#
后跟著 3 個或 6 個十六進(jìn)制的數(shù)字。
用例:
let regexp = /你的正則表達(dá)式/g;
let str = "color: #3f3; background-color: #AA00ef; and: #abcd";
alert( str.match(regexp) ); // #3f3 #AA00ef
P.S. 必須只匹配 3 位或 6 位十六進(jìn)制數(shù)字的顏色值。不應(yīng)該匹配 4 位數(shù)字的值,例如 #abcd
。
查找 #
號后跟著 3 位十六進(jìn)制數(shù)的顏色值 #abc
的正則表達(dá)式:/#[a-f0-9]{3}/i
。
我們可以再添加 3 位可選的十六進(jìn)制數(shù)字。這樣剛好,不多不少。只匹配 #
號后跟著 3 位或 6 位十六進(jìn)制數(shù)字的顏色值。
我們使用量詞 {1,2}
來實(shí)現(xiàn):所以正則表達(dá)式為 /#([a-f0-9]{3}){1,2}/i
。
這里將模式 [a-f0-9]{3}
用括號括起來,以在其外面應(yīng)用量詞 {1,2}
。
用例:
let regexp = /#([a-f0-9]{3}){1,2}/gi;
let str = "color: #3f3; background-color: #AA00ef; and: #abcd";
alert( str.match(regexp) ); // #3f3 #AA00ef #abc
這里存在一個小問題:上面的模式會匹配 #abcd
中的 #abc
。為避免這一問題,我們可以在最后添加 \b
。
let regexp = /#([a-f0-9]{3}){1,2}\b/gi;
let str = "color: #3f3; background-color: #AA00ef; and: #abcd";
alert( str.match(regexp) ); // #3f3 #AA00ef
編寫一個正則表達(dá)式,找出所有十進(jìn)制數(shù)字,包括整數(shù)、浮點(diǎn)數(shù)和負(fù)數(shù)。
用例:
let regexp = /你的正則表達(dá)式/g;
let str = "-1.5 0 2 -123.4.";
alert( str.match(regexp) ); // -1.5, 0, 2, -123.4
帶有可選小數(shù)部分的正數(shù):\d+(\.\d+)?
。
讓我們在開頭加上可選的 -
:
let regexp = /-?\d+(\.\d+)?/g;
let str = "-1.5 0 2 -123.4.";
alert( str.match(regexp) ); // -1.5, 0, 2, -123.4
一個算術(shù)表達(dá)式由 2 個數(shù)字和一個它們之間的運(yùn)算符組成,例如:
1 + 2
1.2 * 3.4
-3 / -6
-2 - 2
運(yùn)算符為 "+"
、"-"
、"*"
或 "/"
中之一。
在開頭、之間的部分或末尾可能有額外的空格。
創(chuàng)建一個函數(shù) parse(expr)
,它接受一個表達(dá)式作為參數(shù),并返回一個包含 3 個元素的數(shù)組:
用例:
let [a, op, b] = parse("1.2 * 3.4");
alert(a); // 1.2
alert(op); // *
alert(b); // 3.4
匹配數(shù)字的正則表達(dá)式:-?\d+(\.\d+)?
。我們在上一題創(chuàng)建了這個表達(dá)式。
我們可以使用 [-+*/]
匹配運(yùn)算符。連字符 -
在方括號中的最前面,因?yàn)樵谥虚g它表示字符范圍,而我們只想讓其表示字符 -
。
在 JavaScript 正則表達(dá)式 /.../
中,我們應(yīng)該對 /
進(jìn)行轉(zhuǎn)義,稍后我們會對其進(jìn)行處理。
我們需要一個數(shù)字、一個運(yùn)算符以及另一個數(shù)字。其間可能會有空格。
完整的正則表達(dá)式為:-?\d+(\.\d+)?\s*[-+*/]\s*-?\d+(\.\d+)?
。
它包含 3 個部分,以 \s*
分隔:
-?\d+(\.\d+)?
—— 第一個數(shù)字,[-+*/]
—— 運(yùn)算符,-?\d+(\.\d+)?
—— 第二個數(shù)字。為了使這里的每一部分成為結(jié)果數(shù)組中的單獨(dú)元素,所以我們把它們括在括號里:(-?\d+(\.\d+)?)\s*([-+*/])\s*(-?\d+(\.\d+)?)
。
使用示例:
let regexp = /(-?\d+(\.\d+)?)\s*([-+*\/])\s*(-?\d+(\.\d+)?)/;
alert( "1.2 + 12".match(regexp) );
結(jié)果包括:
result[0] == "1.2 + 12"
(完整的匹配項)result[1] == "1.2"
(第一組 (-?\d+(\.\d+)?)
—— 第一個數(shù)字,包括小數(shù)部分)result[2] == ".2"
(第二組 (\.\d+)?
—— 第一個數(shù)字的小數(shù)部分)result[3] == "+"
(第三組 ([-+*\/])
—— 運(yùn)算符)result[4] == "12"
(第四組 (-?\d+(\.\d+)?)
—— 第二個數(shù)字)result[5] == undefined
(第五組 (\.\d+)?
—— 第二個數(shù)字的小數(shù)部分不存在,所以這里是 undefined)
我們只想要數(shù)字和運(yùn)算符,不需要完全匹配的以及小數(shù)部分結(jié)果,所以讓我們稍微“清理”一下結(jié)果。
我們可以使用數(shù)組的 shift
方法 result.shift()
來刪去完全匹配的結(jié)果(數(shù)組的第一項)。
可以通過在開頭添加 ?:
來排除包含小數(shù)部分(數(shù)字 2 和 4)(.\d+)
的組:(?:\.\d+)?
。
最終的解決方案:
function parse(expr) {
let regexp = /(-?\d+(?:\.\d+)?)\s*([-+*\/])\s*(-?\d+(?:\.\d+)?)/;
let result = expr.match(regexp);
if (!result) return [];
result.shift();
return result;
}
alert( parse("-1.23 * 3.45") ); // -1.23, *, 3.45
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: