本文 AI 產出,尚未審核

JavaScript 字串操作 — 正規表達式(RegExp)

簡介

在日常的前端開發中,字串驗證、搜尋與取代是最常見的需求之一。雖然 String 內建的方法已能處理簡單的情境,但面對複雜的模式比對時,正規表達式(RegExp)則是不可或缺的利器。透過 RegExp,我們可以在單行程式碼內完成 email、電話號碼、網址等格式的驗證,或是一次抓取所有符合條件的子字串,極大提升程式的可讀性與效能。

本篇文章將從 RegExp 的語法基礎實作範例常見陷阱,一路帶到 實務應用,幫助初學者快速上手,同時提供中階開發者優化技巧,讓你在 JavaScript 中玩轉字串,寫出更健全的程式碼。


核心概念

1. RegExp 物件的建立

在 JavaScript 中,正規表達式有兩種建立方式:

// 方式一:字面量
const pattern1 = /abc/;

// 方式二:建構子
const pattern2 = new RegExp('abc');

字面量寫法較為直觀,且可直接加入 旗標(flags),如 i(不區分大小寫)、g(全域搜尋)與 m(多行模式):

// 不區分大小寫且全域搜尋
const emailRegex = /[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i;

2. 常用的元字符(Metacharacters)

元字符 說明
. 匹配除換行符之外的任意單一字元
\d 匹配任意數字,等價於 [0-9]
\w 匹配字母、數字或底線,等價於 [A-Za-z0-9_]
\s 匹配任意空白字元(空格、Tab、換行)
^ 錨點,表示字串開頭
$ 錨點,表示字串結尾
* 前面的子表達式出現 0 次或多次
+ 前面的子表達式出現 1 次或多次
? 前面的子表達式出現 0 次或 1 次
` `
() 捕獲群組,用於提取子字串或設定優先順序
[] 字元集合,例如 [aeiou] 匹配任一母音

3. 旗標(flags)的意義

旗標 功能
g 全域搜尋,返回所有匹配結果(matchAllreplace 會受影響)
i 不區分大小寫
m 多行模式,^$ 會匹配每一行的起始與結束
s . 可以匹配換行符(ES2018+)
u 以 Unicode 方式處理(支援 Emoji、補充平面)
y 粘性搜尋(只在 lastIndex 位置開始匹配)

程式碼範例

以下示範 5 個在日常開發中常會用到的 RegExp 範例,並加上詳細註解說明。

1. Email 格式驗證

// Email 正規表達式(簡化版,實務上可依需求調整)
const emailPattern = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i;

/**
 * 判斷字串是否為合法的 Email
 * @param {string} str 輸入字串
 * @returns {boolean}
 */
function isValidEmail(str) {
  return emailPattern.test(str);
}

// 範例測試
console.log(isValidEmail('john.doe@example.com')); // true
console.log(isValidEmail('invalid_email@com'));    // false

重點:使用 ^$ 錨點確保整個字串必須完整符合模式,i 旗標讓大小寫不敏感。

2. 台灣手機號碼驗證

// 台灣手機號碼:09xx-xxxxxx 或 09xxxxxxxx
const twMobilePattern = /^09\d{2}-?\d{6}$/;

/**
 * 檢查是否為有效的台灣手機號碼
 */
function isTaiwanMobile(num) {
  return twMobilePattern.test(num);
}

console.log(isTaiwanMobile('0912-345678')); // true
console.log(isTaiwanMobile('0912345678'));  // true
console.log(isTaiwanMobile('1234-567890')); // false

技巧-? 表示「連字符可以出現也可以不出現」,讓兩種常見寫法都能匹配。

3. 從文字中抓取所有網址

// 簡易網址匹配(支援 http/https、www、子域名與路徑)
const urlPattern = /https?:\/\/(?:www\.)?[\w.-]+(?:\.[a-z]{2,})+(?:\/[\w./?%&=-]*)?/gi;

/**
 * 從給定文字中找出所有網址
 * @param {string} text
 * @returns {Array<string>}
 */
function extractUrls(text) {
  // matchAll 會返回 Iterator,使用展開運算子轉成陣列
  return [...text.matchAll(urlPattern)].map(m => m[0]);
}

const sample = `
  這裡有兩個連結:
  1. https://developer.mozilla.org/zh-TW/
  2. http://example.com/path?query=1
`;
console.log(extractUrls(sample));
// [ 'https://developer.mozilla.org/zh-TW/', 'http://example.com/path?query=1' ]

說明:使用 g 旗標取得所有匹配,i 讓協議部份不區分大小寫。

4. 替換字串中的多個空白為單一空格

// 匹配一個或多個連續的空白字元
const multiSpacePattern = /\s+/g;

/**
 * 正規化字串空白
 */
function normalizeSpaces(str) {
  return str.replace(multiSpacePattern, ' ').trim();
}

const messy = '  JavaScript    正規表達式   教學   ';
console.log(normalizeSpaces(messy)); // "JavaScript 正規表達式 教學"

提示trim() 先移除首尾空白,再用 replace 把中間的多個空白壓縮成單一空格。

5. 使用捕獲群組(Capture Group)拆分日期

// 日期格式:YYYY/MM/DD 或 YYYY-MM-DD
const datePattern = /^(\d{4})[-\/](\d{2})[-\/](\d{2})$/;

/**
 * 把日期字串分割成 [year, month, day]
 * @param {string} dateStr
 * @returns {Array<string>|null}
 */
function splitDate(dateStr) {
  const match = datePattern.exec(dateStr);
  if (!match) return null;
  // match[1] = year, match[2] = month, match[3] = day
  return [match[1], match[2], match[3]];
}

console.log(splitDate('2023-11-19')); // ["2023","11","19"]
console.log(splitDate('2023/11/19')); // ["2023","11","19"]

重點() 形成捕獲群組,exec 會回傳完整匹配與每個群組的內容,方便後續處理。


常見陷阱與最佳實踐

陷阱 說明 解決方法
忘記加錨點 (^$) 正則會匹配子字串,導致驗證不嚴格。 在需要完整匹配時務必加上 ^$
全域旗標 gtest() 結合 test() 會改變 RegExp 的 lastIndex,連續呼叫可能得到錯誤結果。 每次使用前手動 regex.lastIndex = 0,或直接移除 g
字元集合的「-」誤用 [-a] 會被解析為範圍,可能產生預期外的匹配。 若要匹配 - 本身,置於集合最前或最後,或使用 \ 逃脫。
Unicode 字元(Emoji)匹配失敗 默認正則只支援 BMP,對於補充平面會被切成兩個 surrogate。 加上 u 旗標,或使用 \p{Emoji}(需要支援的環境)。
過度複雜的單行正則 可讀性差,維護困難。 盡量拆成多個小正則或使用命名捕獲群組(ES2018+ (?<name>…))。

最佳實踐

  1. 先寫測試:使用 Jest 或 Vitest 撰寫單元測試,確保正則行為符合預期。
  2. 保持簡潔:若正則超過 80 個字元,考慮拆解或加上註解說明。
  3. 使用 RegExp 靜態方法String.prototype.matchAllreplaceAll(ES2021)可提升可讀性。
  4. 避免硬編碼:將常用模式抽成常量或函式,提升重用性。

實際應用場景

場景 需求 正則解法
表單驗證 Email、電話、身分證號碼 直接在 onSubmit 前使用 test()match()
文字搜尋 高亮關鍵字、搜尋結果分頁 new RegExp(keyword, 'gi') 搭配 replace 包裝 <mark> 標籤。
日誌分析 從伺服器 log 抽取 IP、時間戳 (\d{1,3}\.){3}\d{1,3} 抓取 IPv4,/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/ 抓時間。
內容過濾 移除 HTML 標籤、清除敏感字 /\<[^>]*\>/g 移除所有 HTML,或 `/\b(word1
資料轉換 把 CSV 文字拆成陣列 /,(?=(?:[^"]*"[^"]*")*[^"]*$)/ 處理含引號的欄位。

範例:在聊天室中即時高亮使用者輸入的關鍵字:

function highlight(text, keyword) {
  const escKeyword = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // 逃脫特殊字元
  const regex = new RegExp(`(${escKeyword})`, 'gi');
  return text.replace(regex, '<mark>$1</mark>');
}

console.log(highlight('JavaScript 正規表達式很強大', '正規表達式'));
// => "JavaScript <mark>正規表達式</mark>很強大"

總結

正規表達式是 字串處理的萬能工具,在 JavaScript 中只要掌握以下三點,就能在日常開發中如虎添翼:

  1. 語法與旗標:熟悉元字符、錨點與常用旗標的意義,避免因忘記 ^$g 而產生錯誤匹配。
  2. 實作範例:透過實際的 Email、電話、URL、空白正規化與日期拆解等範例,了解 testexecmatchAllreplace 的使用差異。
  3. 防坑與最佳實踐:注意 lastIndex、Unicode、字元集合的寫法,並將正則抽成常量、寫測試、保持可讀性。

只要在開發過程中適時使用 RegExp,從 表單驗證文字搜尋日誌分析資料清理,都能大幅減少程式碼量、提升效能,讓你的 JavaScript 應用更可靠、更易維護。祝你玩轉正規表達式,寫出更乾淨、更強大的程式!