溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

js之正則表達(dá)式回溯的示例分析

發(fā)布時(shí)間:2021-09-06 16:52:10 來(lái)源:億速云 閱讀:110 作者:小新 欄目:互聯(lián)網(wǎng)科技

這篇文章主要介紹了js之正則表達(dá)式回溯的示例分析,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

當(dāng)一個(gè)正則表達(dá)式掃描目標(biāo)字符串時(shí),從左到右逐個(gè)掃描正則表達(dá)式的組成部分,在每個(gè)位置上測(cè)試能不能找到一個(gè)匹配。對(duì)于每一個(gè)量詞和分支,都必須確定如何繼續(xù)進(jìn)行。如果是一個(gè)量詞(如*、+?或者{2,}),那么正則表達(dá)式必須確定何時(shí)嘗試匹配更多的字符;如果遇到分支(通過(guò)|操作符),那么正則表達(dá)式必須從這些選項(xiàng)中選擇一個(gè)進(jìn)行嘗試。

當(dāng)正則表達(dá)式做出這樣的決定時(shí),如果有必要,它會(huì)記住另一個(gè)選項(xiàng),以備返回后使用。如果所選方案匹配成功,正則表達(dá)式將繼續(xù)掃描正則表達(dá)式模板,如果其余部分匹配也成功了,那么匹配就結(jié)束了。但是,如果所選擇的方案未能發(fā)現(xiàn)相應(yīng)匹配,或者后來(lái)的匹配也失敗了,正則表達(dá)式將回溯到最后一個(gè)決策點(diǎn),然后在剩余的選項(xiàng)中選擇一個(gè)。繼續(xù)這樣,直到找到一個(gè)匹配,或者量詞和分支選項(xiàng)的所有可能的排列組合都嘗試失敗后放棄這一過(guò)程,然后移動(dòng)到此過(guò)程開(kāi)始位置的下一個(gè)字符上,重復(fù)此過(guò)程。

例如,下面的代碼演示了這一過(guò)程是如何通過(guò)回溯處理分支的。

/h(ello|appy) hippo/.test("hello there, happy hippo");

上面一行正則表達(dá)式用于匹配“hello hippo”或“happy hippo”。測(cè)試一開(kāi)始要查找一個(gè)h,目標(biāo)字符串的第一個(gè)字母恰好就是h,立刻就找到了。接下來(lái),子表達(dá)式(ello|appy)提供了兩個(gè)處理選項(xiàng)。正則表達(dá)式選擇最左邊的選項(xiàng)(分支選擇總是從左到右進(jìn)行),檢查ello 是否匹配字符串的下一個(gè)字符,確實(shí)匹配,然后正則表達(dá)式又匹配了后面的空格。

然而,在接下來(lái)的匹配中正則表達(dá)式“走進(jìn)了死胡同”,因?yàn)閔ippo 中的h 不能匹配字符串中的下一個(gè)字母t。此時(shí)正則表達(dá)式還不能放棄,因?yàn)樗€沒(méi)有嘗試過(guò)所有的選擇,隨后它回溯到最后一個(gè)檢查點(diǎn)(在匹配了首字母h 之后的那個(gè)位置上)并嘗試匹配第二個(gè)分支選項(xiàng)。但由于匹配沒(méi)有成功,而且也沒(méi)有更多的選項(xiàng)了,正則表達(dá)式認(rèn)為從字符串的第一個(gè)字符開(kāi)始匹配是不能成功的,因此它從第二個(gè)字符開(kāi)始重新進(jìn)行查找。正則表達(dá)式?jīng)]有找到h,繼續(xù)向后找,直到第14 個(gè)字母才找到,它匹配happy 的那個(gè)h。隨后正則表達(dá)式再次進(jìn)入分支過(guò)程,這次ello 未能匹配,但在回溯之后的第二次分支中,它匹配了整個(gè)字符串“happy hippo”,匹配成功了。

再如,下面代碼演示了帶重復(fù)量詞的回溯。

var str = "<p>Para 1.</p>" +"<img src='smiley.jpg'>" +"<p>Para 2.</p>" +"<div>Div.</div>";
/<p>.*<\/p>/i.test(str);

正則表達(dá)式先匹配了字符串開(kāi)始的3個(gè)字母<p>,然后是.*。點(diǎn)號(hào)表示匹配除換行符以外的任意字符,星號(hào)這個(gè)“貪婪”量詞表示重復(fù)零次或多次,匹配盡量多的次數(shù)。因?yàn)槟繕?biāo)字符串中沒(méi)有換行符,正則表達(dá)式將匹配剩下的全部字符串!不過(guò)由于正則表達(dá)式模板中還有更多內(nèi)容需要匹配,所以正則表達(dá)式嘗試匹配<。由于在字符串末尾匹配不成功,因此每次回溯一個(gè)字符,繼續(xù)嘗試匹配<,直到正則表達(dá)式回到</div>標(biāo)簽的<位置。接下來(lái)嘗試匹配\/(轉(zhuǎn)義反斜杠),匹配成功,然后匹配p,匹配不成功。正則表達(dá)式繼續(xù)回溯,重復(fù)此過(guò)程,直到第二段末尾時(shí)終于匹配了</p>。匹配返回成功需要從第一段頭部一直掃描到最后一個(gè)的末尾,這可能不是我們想要的結(jié)果。

將正則表達(dá)式中的“貪婪”量詞*改為“懶惰”(又名“非貪婪”)量詞*?,以匹配單個(gè)段落?!皯卸琛绷吭~的回溯工作以相反方式進(jìn)行。當(dāng)正則表達(dá)式/<p>.*?<\/p>/推進(jìn)到.*?時(shí),首先嘗試全部跳過(guò),然后繼續(xù)匹配<\/p>。

這樣做是因?yàn)??匹配零次或多次,盡可能少重復(fù),盡可能少意味著可以重復(fù)零次。但是,當(dāng)隨后的<在字符串的這一點(diǎn)上匹配失敗時(shí),正則表達(dá)式回溯并嘗試下一個(gè)最小的字符數(shù):1個(gè)。正則表達(dá)式繼續(xù)像這樣向前回溯到第一段的末尾,在那里量詞后面的<\/p>得到完全匹配。

如果目標(biāo)字符串只有一個(gè)段落,那么此正則表達(dá)式的“貪婪”版本和“懶惰”版本是等價(jià)的,但嘗試匹配的過(guò)程不同。

當(dāng)一個(gè)正則表達(dá)式占用瀏覽器幾秒甚至更長(zhǎng)時(shí)間時(shí),問(wèn)題原因很可能是回溯失控。為說(shuō)明此問(wèn)題,給出下面的正則表達(dá)式,它的目標(biāo)是匹配整個(gè)HTML文件。此表達(dá)式被拆分成多行是為了適合頁(yè)面顯示。與其他正則表達(dá)式不同,JavaScript在沒(méi)有選項(xiàng)時(shí)可使點(diǎn)號(hào)匹配任意字符,包括換行符,所以此例中以[\s\S]匹配任意字符。

/<html>[\s\S]*?<head>[\s\S]*?<title>[\s\S]*?<\/title>[\s\S]*?<\/head>
[\s\S]*?<body>[\s\S]*?<\/body>[\s\S]*?<\/html>/

此正則表達(dá)式匹配在正常HTML 字符串時(shí)工作良好,但當(dāng)目標(biāo)字符串缺少一個(gè)或多個(gè)標(biāo)簽時(shí),就會(huì)變得十分糟糕。例如</html>標(biāo)簽缺失,最后一個(gè)[\s\S]*?將擴(kuò)展到字符串的末尾,因?yàn)樵谀抢餂](méi)有發(fā)現(xiàn)</html>標(biāo)簽,然后正則表達(dá)式將查看此前的[\s\S]*?隊(duì)列記錄的回溯位置,使它們進(jìn)一步擴(kuò)大。正則表達(dá)式嘗試擴(kuò)展倒數(shù)第二個(gè)[\s\S]*?—用它匹配</body>標(biāo)簽,就是此前匹配過(guò)正則表達(dá)式模板<\/body>的那個(gè)標(biāo)簽,然后繼續(xù)查找第二個(gè)</body>標(biāo)簽,直到字符串的末尾。當(dāng)所有這些步驟都失敗時(shí),倒數(shù)第三個(gè)[\s\S]*?將被擴(kuò)展,直至字符串的末尾,依此類推。

此類問(wèn)題的解決辦法在于盡可能具體地指出分隔符之間的字符匹配形式,如模板“.*?”用于匹配雙引號(hào)包圍的一個(gè)字符串。用更具體的[^"\rn]*取代過(guò)于寬泛的.*?就去除了回溯時(shí)可能發(fā)生的幾種情況,如嘗試用點(diǎn)號(hào)匹配引號(hào),或者擴(kuò)展搜索超出預(yù)期范圍。

在HTML 的例子中解決辦法不是那么簡(jiǎn)單。不能使用否定字符類型,如用[^<]替代[\s\S],因?yàn)樵谒阉鬟^(guò)程中可能會(huì)遇到其他類型的標(biāo)簽。但是,可以通過(guò)重復(fù)一個(gè)非捕獲組來(lái)達(dá)到同樣效果,它包含一個(gè)回溯(阻塞下一個(gè)所需的標(biāo)簽)和[\s\S](任意字符)元序列。這樣可以確保中間位置上查找的每個(gè)標(biāo)簽都會(huì)失敗。然后,更重要的是,[\s\S]模板在回溯過(guò)程中阻塞的標(biāo)簽在被發(fā)現(xiàn)之前不能被擴(kuò)展。應(yīng)用此方法后對(duì)正則表達(dá)式的最終修改如下:

/<html>(?:(?!<head>)[\s\S])*<head>(?:(?!<title>)[\s\S])*<title>

(?:(?!<\/title>)[\s\S])*<\/title>(?:(?!<\/head>)[\s\S])*<\/head>

(?:(?!<body>)[\s\S])*<body>(?:(?!<\/body>)[\s\S])*<\/body>
(?:(?!<\/html>)[\s\S])*<\/html>/

雖然這樣做消除了潛在的回溯失控,并允許正則表達(dá)式在匹配不完整HTML字符串失敗時(shí)的使用時(shí)間與文本長(zhǎng)度呈線性關(guān)系,但是正則表達(dá)式的效率并沒(méi)有提高。像這樣為每個(gè)匹配字符進(jìn)行多次前瞻,缺乏效率,而且成功匹配過(guò)程也相當(dāng)慢。匹配較短字符串時(shí)使用此方法相當(dāng)不錯(cuò),而匹配一個(gè)HTML 文件可能需要前瞻并測(cè)試上千次。

感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“js之正則表達(dá)式回溯的示例分析”這篇文章對(duì)大家有幫助,同時(shí)也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識(shí)等著你來(lái)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI