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()