1462 字
7 分鐘

RegExp.escape() 怎麼用?動態建立 JavaScript 正則表達式先處理輸入

只要把使用者輸入、搜尋關鍵字或外部設定放進 new RegExp(),就不能把它當成普通字串直接串接。輸入裡的 ., *, (、反斜線或 / 可能改變正則表達式的語意;某些看似只是標點的字元,放在錯誤位置還可能造成 syntax error。

現在 JavaScript 有專門的 RegExp.escape():它把字串轉成可以安全放進正則表達式、代表「字面值」的片段。 最常見的用法是:

const keyword = "price.*(sale)";
const pattern = new RegExp(RegExp.escape(keyword), "iu");
pattern.test("PRICE.*(SALE)"); // true

這裡的重點是「把輸入當 literal 搜尋」,不是讓使用者輸入正則語法。若產品真的要讓使用者撰寫 regex,應另做語法限制、錯誤處理與資源上限,不能用 RegExp.escape() 取代那套設計。

為什麼手寫 replace 不夠#

很多舊範例會用一段 replace 把 . * + ? ^ $ { } ( ) | [ ] \ 前面加反斜線。這類程式碼常漏掉 /、連字號、換行或其他需要在不同 regex context 特別處理的字元,也容易在字串前面再接上 \1、\u 等 escape 時產生歧義。

RegExp.escape() 的規則不只是「每個特殊字元前面加反斜線」:

輸入情況轉換目的
正則語法字元與 /避免被解讀成 pattern syntax 或 literal delimiter
字串開頭的 ASCII 英文字母或數字用 \x 表示,避免和前面既有的 escape 串在一起
- 等 punctuator使用 \x 形式,避免某些 context 中無法只靠反斜線安全表示
空白、控制字元與 lone surrogate依規則轉成可放入 pattern 的表示

因此,不要把輸出長相當成「醜陋但多餘」。例如:

RegExp.escape("foo-bar.*");
// "\\x66oo\\x2dbar\\.\\*"

前面的 f 變成 \x66,連字號變成 \x2d,這正是 API 要避免相鄰 escape 改變語意的地方。

動態搜尋的完整寫法#

假設頁面有一個搜尋框,需求是找出包含使用者輸入的文字;輸入應該只代表 literal,不應該變成 regex:

function containsKeyword(text, keyword) {
const pattern = new RegExp(RegExp.escape(keyword), "iu");
return pattern.test(text);
}
containsKeyword("Release v26.9.0", "v26.9.0"); // true
containsKeyword("a+b", "a+b"); // true

如果要組合一小段固定 regex 語法,也只逃逸變動的部分,不要把整個 pattern 都包起來:

function matchesHost(url, host) {
const literalHost = RegExp.escape(host);
const pattern = new RegExp(
"^https?://" + literalHost + "(?=/|$)",
"i",
);
return pattern.test(url);
}
matchesHost("https://example.com/docs", "example.com"); // true
matchesHost("https://exampleXcom/docs", "example.com"); // false

這種寫法讓 ^https?:// 和 lookahead 保留程式設計者定義的語意,只有 host 被視為外部 literal。若 host 也來自不可信輸入,仍需在 URL parser、允許的 domain 與 regex 三個層次分別驗證。

encodeURIComponent() 不能代替 RegExp.escape()#

encodeURIComponent() 的目標是 URI component,不是 regex pattern。它會產生 % 編碼,無法保證輸入在 new RegExp() 裡代表原字串;反過來,regex escape 也不能拿來做 URL encoding。

const keyword = "a+b/c";
new RegExp(RegExp.escape(keyword), "u"); // 搜尋 literal "a+b/c"
encodeURIComponent(keyword); // URL 使用的編碼,不是 regex escape

兩者要依資料會進入的語法層次分開處理。若一段輸入先進 URL、再進 regex,應先設計清楚每一層的資料邊界,不要重複或錯用另一種 encoder。

舊環境的 feature detection 與 fallback#

RegExp.escape() 是較新的標準 API。若程式需要跑在尚未提供它的 runtime,先做 feature detection:

function escapeRegexLiteral(value) {
if (typeof RegExp.escape === "function") {
return RegExp.escape(value);
}
throw new Error(
"RegExp.escape() is unavailable; load an approved polyfill before building a pattern.",
);
}

正式專案有兩個選擇:

  1. 將支援矩陣提高到提供 RegExp.escape() 的 runtime。
  2. 依專案的 browser/Node 版本,載入經維護、經測試和資安審查的 polyfill,並把它加入相容性測試。

不要在 fallback 裡臨時補一個只處理 .、* 和 ? 的 replace。它可能在今天的範例通過,卻在連字號、slash、開頭字元或 Unicode 輸入上改變語意。

可重複的最小驗證#

升級 Node.js、瀏覽器或 polyfill 後,可以用一個包含不同邊界的字串做 smoke test:

Terminal window
node -e 'const value = "foo-bar.*"; console.log(typeof RegExp.escape, RegExp.escape(value));'

預期在支援的 runtime 中會得到 function,以及類似 \x66oo\x2dbar.* 的輸出。接著再測試:

  • 輸入開頭是 ASCII 英文字母或數字。
  • 輸入包含 -、/、\、.、*、?、( 和 )。
  • 輸入包含換行、emoji、非 ASCII 字元與 lone surrogate(若應用程式會接收這些資料)。
  • pattern 放在固定語法前後時,仍只匹配 literal,而不會擴大範圍。

這個檢查只能確認 API 和基本輸出,不能代替應用程式的資源限制。若 pattern 仍可能很大,或輸入與固定語法組合後可能造成高成本回溯,還是要設長度限制、測試 worst case,並考慮避免把 regex 當成搜尋引擎。

結論#

當需求是「把外部字串當成正則表達式裡的 literal」時,優先使用 RegExp.escape(),再把結果放進 new RegExp()。它不只是替特殊字元加反斜線,也處理開頭 ASCII 字元、punctuator、slash 與 Unicode 邊界。舊環境先做 feature detection,必要時使用經審查的 polyfill;不要用 encodeURIComponent() 或不完整的手寫 replace 代替。

常見問題#

Q: RegExp.escape() 會讓輸入變成可以執行的 regex 嗎?#

A: 它做相反的事:把輸入轉成 literal 片段,讓輸入中的 regex 語法字元不再改變 pattern。若產品要支援使用者撰寫 regex,不能先 escape 再期待語法保留。

Q: 為什麼第一個字母會變成 \x66?#

A: 標準為字串開頭的 ASCII 英文字母或數字採用 \x 表示,避免它和 pattern 前面已有的數字或 escape 序列相鄰時被解讀成同一個 escape。這不是多餘的格式化,而是降低組合 pattern 時的歧義。

Q: 只要呼叫 RegExp.escape() 就不用限制輸入長度了嗎?#

A: 不用。Escape 解決的是語法與字面值邊界,不會替你處理超長輸入、regex 執行時間、資源耗用或固定語法組合後的回溯風險。仍要設長度上限並測試最壞情況。

參考資料:

MDN:RegExp.escape()

ECMAScript Specification:RegExp.escape

RegExp.escape() 怎麼用?動態建立 JavaScript 正則表達式先處理輸入
https://laplusda.com/posts/javascript-regexp-escape-guide/
作者
Zero
發佈於
2026-09-19
許可協議
CC BY-NC-SA 4.0
這篇文章有幫助嗎?

回報錯字、失效連結,或告訴我你想看的延伸主題。