?
一、正則表達式入門?
正則表達式本質上是一串字符序列,用于定義一個文本模式。通過這個模式,我們可以指定要匹配的文本特征。例如,如果你想匹配一個以 “abc” 開頭的字符串,正則表達式可以寫作 “^abc”,其中 “^” 表示匹配字符串的開頭。?
(一)基本字符匹配?
在正則表達式中,普通字符(如字母、數字)通常表示它們自身。例如,正則表達式 “abc” 可以匹配文本中的 “abc” 序列。如果文本是 “abcdef”,那么 “abc” 會匹配成功;但如果文本是 “defabc”,那么匹配失敗,因為正則表達式是從左到右進行匹配的。?
案例(JavaScript):?
const text1 = "abcdef";?const regex1 = /abc/;?console.log(regex1.test(text1)); // 輸出: true??const text2 = "defabc";?const regex2 = /abc/;?console.log(regex2.test(text2)); // 輸出: false?
?
在上述代碼中,/abc/是一個正則表達式字面量,test方法用于測試給定的字符串是否包含匹配正則表達式的文本。?
(二)特殊字符?
正則表達式中有一些特殊字符,它們具有特殊的含義。例如:?
“.”(點號):匹配任意單個字符(除了換行符)。例如,“a.c” 可以匹配 “abc”、“adc” 等,但不能匹配 “ac”。?
“”(星號):表示前面的字符可以出現零次或多次。例如,“abc” 可以匹配 “ac”、“abc”、“abbc” 等。?
“+”(加號):表示前面的字符至少出現一次。例如,“ab+c” 可以匹配 “abc”、“abbc” 等,但不能匹配 “ac”。?
“?”(問號):表示前面的字符可以出現零次或一次。例如,“ab?c” 可以匹配 “ac” 和 “abc”。?
“”(脫字符):用于匹配字符串的開頭。例如,“abc” 表示匹配以 “abc” 開頭的字符串。?
“?
” 表示匹配以 “abc” 結尾的字符串。?
?
// “.”的案例?const text3 = "a1c a2c a c";?const regex3 = /a.c/;?console.log(text3.match(regex3)); // 輸出: ["a1c"],注意:match方法默認只返回第一個匹配結果??// “*”的案例?const text4 = "ac abc abbc abbbc";?const regex4 = /ab*c/;?console.log(text4.match(regex4)); // 輸出: ["abbbc"]??// “+”的案例?const text5 = "abc abbc abbbc";?const regex5 = /ab+c/;?console.log(text5.match(regex5)); // 輸出: ["abbbc"]??// “?”的案例?const text6 = "ac abc";?const regex6 = /ab?c/;?console.log(text6.match(regex6)); // 輸出: ["abc"]??// “^”的案例?const text7 = ["abcdef", "defabc", "abc"];?const regex7 = /^abc/;?text7.forEach(str => {?console.log(regex7.test(str)); ?// 輸出: true, false, true?});??// “$”的案例?const text8 = ["abc", "defabc", "abcd"];?const regex8 = /abc$/;?text8.forEach(str => {?console.log(regex8.test(str)); ?// 輸出: true, true, false?});?
?
在這些案例中,match方法用于在字符串中查找所有匹配正則表達式的子字符串,并返回一個數組。如果沒有找到匹配項,則返回null。test方法則用于判斷字符串是否與正則表達式匹配,返回true或false。?
(三)字符集?
字符集用于匹配一組特定的字符。例如,“[abc]” 可以匹配 “a”、“b” 或 “c”。字符集還可以使用范圍表示法,如 “[a-z]” 表示匹配任意一個小寫字母,“[0-9]” 表示匹配任意一個數字。?
// 匹配元音字母?const text9 = "apple banana cherry";?const regex9 = /[aeiou]/g;?console.log(text9.match(regex9)); ?// 輸出: ["a", "e", "a", "a", "e"],這里的“g”標志表示全局匹配,即找到所有匹配項??// 匹配數字?const text10 = "123abcABC";?const regex10 = /[0-9]/g;?console.log(text10.match(regex10)); // 輸出: ["1", "2", "3"]??// 匹配字母?const text11 = "123abcABC";?const regex11 = /[a-zA-Z]/g;?console.log(text11.match(regex11)); // 輸出: ["a", "b", "c", "A", "B", "C"]?
?
在上述代碼中,正則表達式中的 “g” 標志是非常重要的,它使得match方法能夠返回所有匹配的結果,而不僅僅是第一個。?
(四)分組?
分組是通過圓括號 “()” 來實現的。它可以將多個字符組合在一起,作為一個整體進行匹配。例如,“(abc)+” 表示匹配一個或多個 “abc” 序列。?
const text12 = "abcabcabc abcab ab";?const regex12 = /(abc)+/g;?console.log(text12.match(regex12)); ?// 輸出: ["abcabcabc", "abcab"]??
在這個例子中,通過分組我們能夠準確匹配由一個或多個 “abc” 序列組成的子字符串。?
二、正則表達式的進階應用?
(一)匹配特定格式的文本?
正則表達式可以用來匹配各種特定格式的文本,如日期、電話號碼、電子郵件地址等。例如,一個簡單的電子郵件地址匹配正則表達式可以寫作 “[a-zA-Z0-9_.±]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+”。這個表達式通過字符集和分組的方式,確保了電子郵件地址的基本格式。?
// 日期匹配?const text13 = "今天是2023-10-15,明天是2023-10-16";?const regex13 = /\d{4}-\d{2}-\d{2}/g;?console.log(text13.match(regex13)); ?// 輸出: ["2023-10-15", "2023-10-16"]??// 電話號碼匹配?const text14 = "聯系電話:13800138000";?const regex14 = /1\d{10}/;?console.log(text14.match(regex14)); // 輸出: ["13800138000"]??// 電子郵件地址匹配?const text15 = "我的郵箱是example@example.com,另一個是test_123@test-company.co.uk";?const regex15 = /[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+/g;?console.log(text15.match(regex15)); ?// 輸出: ["example@example.com", "test_123@test-company.co.uk"]??
這些案例展示了如何使用正則表達式在 JavaScript 中準確匹配常見的特定格式文本。?
(二)捕獲與替換?
正則表達式不僅可以用于匹配文本,還可以用于捕獲和替換文本。通過分組和捕獲功能,我們可以提取文本中的特定部分,并進行替換操作。例如,在文本編輯器中,我們可以使用正則表達式查找所有以 “http://” 開頭的鏈接,并將其替換為 “https://”。?
// 捕獲域名部分并替換鏈接協議?const text16 = "請訪問鏈接:http://example.com和http://test.com";?const regex16 = /http://([a-zA-Z0-9-.]+)/g;?const newText16 = text16.replace(regex16, "https://$1");?console.log(newText16); ?// 輸出: 請訪問鏈接:https://example.com和https://test.com??// 提取價格部分?const text17 = "蘋果價格是10元,香蕉價格是5元";?const regex17 = /價格是 (\d+) 元/g;?const prices = text17.match(regex17);?if (prices) {?prices.forEach(price => {?const num = price.match(/\d+/)[0];?console.log(parseInt(num)); ?// 輸出: 10, 5?});?}?
?
在第一個替換案例中,replace方法接受一個正則表達式和一個替換字符串,其中 “$1” 表示引用第一個捕獲組的內容。在第二個提取案例中,我們先通過正則表達式找到包含價格的子字符串,然后再進一步提取其中的數字。?
(三)貪婪與非貪婪匹配?
在正則表達式中,貪婪模式會盡可能多地匹配字符,而非貪婪模式則會盡可能少地匹配字符。例如,“ab” 是一個貪婪模式,它會匹配 “aabb” 中的 “aabb”;而 “a?b” 是一個非貪婪模式,它會匹配 “aabb” 中的 “ab” 和 “abb”。?
// 貪婪模式案例?const text18 = "aaaab";?const regex18 = /a*b/;?console.log(text18.match(regex18)); // 輸出: ["aaaab"]??// 非貪婪模式案例?const text19 = "aaaab";?const regex19 = /a*?b/;?console.log(text19.match(regex19)); // 輸出: ["ab"]??// 提取HTML標簽內內容的案例?const text20 = "<div>content1</div><div>content2</div>";?const greedyRegex = /<div>.*</div>/;?const nonGreedyRegex = /<div>.*?</div>/g;?console.log(text20.match(greedyRegex)); ?// 輸出: ["<div>content1</div><div>content2</div>"]?console.log(text20.match(nonGreedyRegex)); ?// 輸出: ["<div>content1</div>", "<div>content2</div>"]??
通過這些案例,可以清晰地看到貪婪模式和非貪婪模式在匹配行為上的差異。?
三、正則表達式的高級技巧?
(一)前瞻與后顧?
前瞻(Lookahead)和后顧(Lookbehind)是正則表達式中的高級功能。它們允許我們在匹配時考慮后面的或前面的字符,但不將其包含在匹配結果中。例如,“(?=abc) def” 表示匹配 “def”,但前提是它后面跟著 “abc”。?
// 正向前瞻案例?const text21 = "defabc defxyz";?const regex21 = /(?=abc)def/;?console.log(text21.match(regex21)); // 輸出: ["def"]??// 正向后顧案例?const text22 = "abcdef xyzdef";?const regex22 = /(?<=abc)def/;?console.log(text22.match(regex22)); // 輸出: ["def"]??
在 JavaScript 中,正向后顧在某些環境(如 Chrome 瀏覽器)中支持有限,上述代碼在支持正向后顧的環境中運行。?
(二)遞歸匹配?
遞歸匹配是一種強大的功能,可以用于匹配嵌套的結構,如括號嵌套。例如,“?
(?>[()]+∣(?R))?
” 可以匹配嵌套的括號結構。?
const text23 = "((a + b) * (c - d))";?const regex23 = /\((?>[^()]+|(?R))*\)/;?console.log(text23.match(regex23)); ?// 輸出: ["((a + b) * (c - d))"]??const text24 = "((a + b) * (c - d)";?console.log(text24.match(regex23)); ?// 輸出: null?
?
這個案例展示了遞歸匹配在處理嵌套結構時的有效性,以及在括號不完整時匹配失敗的情況。?
(三)性能優化?
正則表達式的性能優化非常重要,尤其是在處理大量文本時。避免使用過于復雜的正則表達式,盡量減少回溯次數,可以提高匹配效率。?
// 低效的匹配單詞方式?const longText = "這是一篇很長很長的文檔,包含了很多單詞,比如apple, banana, cherry等等";?const inefficientRegex = /[a-zA-Z]+|[^a-zA-Z]+/g;?console.time("inefficientMatch");?longText.match(inefficientRegex);?console.timeEnd("inefficientMatch"); ??// 高效的匹配單詞方式(先分割)?console.time("efficientMatch");?const words = longText.split(/[^a-zA-Z]+/).filter(word => word.length > 0);?console.timeEnd("efficientMatch"); ??
在這個案例中,通過console.time和console.timeEnd方法可以對比兩種方式的執行時間,明顯可以看出先分割的方式在處理大量文本時性能更優。?
四、正則表達式的實踐案例?
(一)數據清洗?
在數據處理中,正則表達式可以用于清洗文本數據。例如,去除文本中的多余空格、特殊字符等。?
// 去除多余空格?const text25 = " 你好,世界! ";?const regex25 = /\s+/g;?const cleanText25 = text25.replace(regex25, " ");?console.log(cleanText25); ?// 輸出: 你好,世界!??// 去除特殊字符?const text26 = "!@#Hello, World$%^";?const regex26 = /[!@#$%^&*(),.?":{}|<>]/g;?const cleanText26 = text26.replace(regex26, "");?console.log(cleanText26); ?// 輸出: Hello, World??
通過這些案例,我們可以看到正則表達式在數據清洗方面的便捷性。?
(二)文本提取?
從網頁或文檔中提取特定信息,如標題、鏈接、日期等,正則表達式是一個非常有用的工具。?
// 從網頁中提取鏈接?const htmlText = "<a href=\"http://example1.com\">鏈接1</a><a href=\"http://example2.com\">鏈接2</a>";?const linkRegex = /<a href="([^"]+)">/g;?const links = htmlText.match(linkRegex);?if (links) {?links.forEach(link => {?const url = link.match(/"([^"]+)"/)[1];?console.log(url); ?// 輸出: http://example1.com, http://example2.com?});?}??// 從文檔中提取標題?const docText = "# 標題1\n內容1\n# 標題2\n內容2";?const titleRegex = /^# (.*)$/gm;?const titles = docText.match(titleRegex);?if (titles) {?titles.forEach(title => {?const titleText = title.match(/# (.*)/)[1];?console.log(titleText); ?// 輸出: 標題1, 標題2?});?}??
這里的 “m” 標志表示多行匹配,使得正則表達式能夠匹配每一行以 “# ” 開頭的內容。?
(三)代碼分析?
在代碼編輯器中,正則表達式可以用于高亮顯示特定的代碼模式,如注釋、關鍵字等。?
const code = "# 這是一個注釋\nprint('Hello, World!')";?const commentRegex = /#.*$/gm;?const highlightedCode = code.replace(commentRegex, match => `<span style="color: green;">${match}</span>`);?console.log(highlightedCode); ?// 輸出: <span style="color: green;"># 這是一個注釋</span>?// print('Hello, World!')??const keywordRegex = /\b(print|def|class|if|else|for|while)\b/g;?const keywordHighlightedCode = highlightedCode.replace(keywordRegex, match?
=> <span style="color: blue;">${match}</span>);?
console.log (keywordHighlightedCode);?
// 輸出: # 這是一個注釋?
// print('Hello, World!')?
// 匹配函數定義?
const code2 = "def add (a, b):\n return a + b";?
const functionRegex = /def\s+(\w+)\s*
?
(.??)
:/gm;?
const functions = code2.match(functionRegex);?
if (functions) {?
functions.forEach(func => {?
const name = func.match(/def\s+(\w+)\s*
?
(.??)
:/)[1];?
console.log(函數名: ${name});?
// 輸出:函數名: add?
});?
}?
// 匹配變量聲明?
const code3 = "int num = 10;\nstring text = 'Hello';";?
const variableRegex = /\b\w+\s+\w+\s*=/g;?
const variables = code3.match (variableRegex);?
if (variables) {?
console.log (variables);?
// 輸出: ["int num =", "string text ="]?
}??