776 字
4 分鐘

JavaScript replaceAll 取代後文字變了?字串搜尋與 $ 替換排錯

把通知模板中的每個 {{name}} 換成使用者名稱,replace() 卻只改第一個。換成 replaceAll() 可以處理全部符合項目;但若名稱剛好含有 $& 或 $$,結果仍可能和原文不同。

搜尋一般文字時,直接把字串交給 replaceAll;替換內容要原樣保留時,使用回傳該內容的 callback。 這兩個選擇分別處理搜尋模式與替換規則。

先確認只改一處,還是替換內容被解讀#

MDN replace 說明指出,字串模式只取代第一個符合項目;第二個參數若是字串,仍會解讀特殊替換模式。$& 表示這次符合的文字,$$ 產生一個 $。

以下每個 JavaScript 區塊可獨立存成 .mjs,使用 Node.js 執行:

import assert from 'node:assert/strict';
const template = '{{name}} / {{name}}';
assert.equal(template.replace('{{name}}', '小明'), '小明 / {{name}}');
assert.equal(template.replaceAll('{{name}}', '小明'), '小明 / 小明');
assert.equal(template.replaceAll('{{name}}', '$&'), template);
assert.equal(template.replaceAll('{{name}}', '$$'), '$ / $');
const name = '$& / $$ / $1';
assert.equal(template.replaceAll('{{name}}', () => name), `${name} / ${name}`);
// 字串方法回傳新值,原字串保持原樣。
assert.equal(template, '{{name}} / {{name}}');
console.log('replacement semantics: PASS');

$1 等捕捉群組規則要配合實際正則群組判讀,不能把每個 $ 後接數字都視為已展開。callback 回傳的字串會直接當成替換內容,適合名稱、備註與外部文字;MDN replaceAll也支援此種形式。

將「一般文字全部取代」寫成明確契約#

若搜尋文字來自輸入框,new RegExp(search, 'g') 會把 .、* 等當成正則語法。直接使用字串搜尋,可以讓 a.b 只符合 a.b。需要動態正則時,可另看 RegExp.escape 的輸入處理。

下面的 helper 限定三個參數都是字串,並拒絕空搜尋值。空字串原本有合法行為,但對模板替換工具而言,把文字插入每個位置容易掩蓋輸入錯誤,因此在應用程式入口先擋住。

import assert from 'node:assert/strict';
function replaceLiteralAll(text, search, replacement) {
if ([text, search, replacement].some(value => typeof value !== 'string')) {
throw new TypeError('text, search and replacement must be strings');
}
if (search.length === 0) throw new RangeError('search must not be empty');
return text.replaceAll(search, () => replacement);
}
assert.equal(replaceLiteralAll('a.b aXb a.b', 'a.b', '$&'), '$& aXb $&');
assert.equal(replaceLiteralAll('aaa', 'aa', 'X'), 'Xa'); // 符合項目不重疊
assert.equal(replaceLiteralAll('aaa', 'a', 'aa'), 'aaaaaa'); // 不反覆掃描插入值
assert.equal(replaceLiteralAll('abc', 'x', ''), 'abc');
assert.equal(replaceLiteralAll('a-a', 'a', ''), '-');
assert.throws(() => replaceLiteralAll('abc', '', 'x'), RangeError);
assert.throws(() => replaceLiteralAll('abc', 'a', null), TypeError);
assert.throws(() => 'abc'.replaceAll(/a/, 'x'), TypeError);
assert.equal('aba'.replaceAll(/a/g, () => '$&'), '$&b$&');
console.log('literal replacement contract: PASS');

這組測試同時確認刪除文字、找不到項目、重疊模式與插入內容含搜尋字串的結果。若輸出看似完全沒變,先比對是否接住回傳值,再檢查搜尋文字的大小寫與實際內容。

原樣保留文字後,還要選對輸出位置#

這個 helper 處理的是文字替換。將結果放進網頁時,純文字顯示可交給 textContent;HTML、URL、SQL 等輸出位置各有自己的編碼與參數化規則。將任意文字原樣插入 innerHTML,仍會讓瀏覽器解讀標記。

2026-11-04 維護流程在 macOS、Node.js v24.13.0 執行上述兩個程式,斷言通過;沒有測試產品模板引擎或瀏覽器畫面。

參考資料:

MDN:String.prototype.replace()

MDN:String.prototype.replaceAll()

ECMAScript:String.prototype.replaceAll

JavaScript replaceAll 取代後文字變了?字串搜尋與 $ 替換排錯
https://laplusda.com/posts/javascript-replaceall-literal-replacement/
作者
Zero
發佈於
2026-11-04
許可協議
CC BY-NC-SA 4.0