在后端渲染配置、前端拼装请求报文或做国际化文案时,我们常遇到同时包含$1与${2}的模板字符串。这两种写法语义接近,但原生字符串处理函数很难一次性吃下。如果只用简单的split再join,遇到${1}里带花括号、或数字后面紧跟普通字符的情况就会断掉。更稳妥的做法是用一条正则把两类占位符都捕获出来,再交给replace的回调函数做映射。

一、占位符的匹配规则拆解
先说$1这种写法:它通常以美元符号开头,后面紧跟一个或多个数字,且数字后不能是左花括号(否则应算${1})。而${2}则是美元符号加左花括号、里面是数字、再跟右花括号。两者可以合并成一条正则:用分支匹配,一组抓$数字,一组抓${数字}。注意要对$做转义,写成$。
下面这条正则就能覆盖常见情况:$(d+)|${(d+)}。前半段$(d+)匹配不带花括号的形式,捕获组1拿到数字;后半段${(d+)}匹配带花括号的形式,捕获组2拿到数字。因为用了或,回调函数里要判断哪个组有值。这样无论模板里混用多少种写法,都能逐个替换。
二、用函数式替换完成映射
JavaScript的String.prototype.replace支持第二个参数传函数。函数接收匹配内容、各个捕获组、偏移量和原串,返回用来替换的文本。我们只要在函数中根据捕获组取出对应数据,就能实现灵活替换。相比传入字符串做替换,函数式不会把$再当成特殊符号,也方便加默认值。
看一段完整示例,假设数据存在数组里:
// 模板与数据
var tpl = '用户$1在${2}购买了$3件${4}';
var data = ['张三', '2023-09-01', 2, '苹果'];
// 正则:匹配 $数字 或 ${数字}
var reg = /$(d+)|${(d+)}/g;
var result = tpl.replace(reg, function (match, p1, p2) {
// p1对应$数字,p2对应${数字}
var index = p1 ? parseInt(p1, 10) : parseInt(p2, 10);
var val = data[index - 1];
// 若数据缺失给默认值
return val === undefined ? '' : String(val);
});
console.log(result);
// 输出:用户张三在2023-09-01购买了2件苹果
上面代码里,reg用了全局标志g,所以会替换所有占位符。回调函数里先判断p1或p2哪个有值,再算数组下标。由于模板里数字从1开始,而数组从0开始,因此用了index - 1。这种写法比手写循环切分字符串要可靠得多。
三、进阶:支持默认值与格式化
真实项目里,占位符可能写成${2:默认名}或要求把数字格式化。我们可以在正则里多加一个捕获组存默认值,然后在函数里判断。如果对应数据为空,就返回默认值。这样模板作者就能在字符串里自描述兜底逻辑。
示例如下:
var tpl = '姓名${1:匿名},年龄$2岁';
var data = [undefined, 18];
// 支持 ${n:默认值}
var reg = /$(d+)|(${(d+)(?::([^}]*))?})/g;
var out = tpl.replace(reg, function (m, p1, p2, p3, p4) {
var idx = p1 ? parseInt(p1, 10) : parseInt(p3, 10);
var def = p4; // 默认值
var v = data[idx - 1];
if (v === undefined || v === null || v === '') {
return def !== undefined ? def : '';
}
return String(v);
});
console.log(out);
// 输出:姓名匿名,年龄18岁
这里正则后半段用(?::([^}]*))?可选组抓冒号后的默认值。虽然正则变长,但可读性仍可控。函数式替换让我们在JS侧做任何加工,比如把日期转成本地格式、把金额除以一百,都不会污染模板本身。
四、与其他方案的对比
有人习惯用第三方模板引擎,比如把${x}丢给现成库。但引入依赖会增加包体,且多数库不认$1这种短写。自己用正则加函数替换,零依赖、可控性强,也更容易单元测试。下表列出差异:
| 方案 | 支持$1 | 支持${n} | 依赖 | 灵活性 |
|---|---|---|---|---|
| 手动split | 弱 | 弱 | 无 | 低 |
| 正则+函数替换 | 强 | 强 | 无 | 高 |
| 模板引擎 | 多数不支持 | 强 | 有 | 中 |
从表里能看出,正则加函数替换在零依赖前提下覆盖了两种占位符,还能顺手做默认值。对小型工具、脚本或嵌入式页面来说,是最省事的做法。只要注意正则转义与下标换算,基本不会踩坑。
五、常见误区提醒
一个容易错的地方是:在replace的字符串参数里写$1会被当成引用捕获组,而不是原样输出。所以如果映射值里含有$符号,必须用函数返回,不能在字符串替换里直接拼。另一个误区是忘记全局标志g,导致只换第一个占位符。只要避开这两点,这套方法就很稳。
总结来看,用一条分支正则捕获$数字与${数字},配合replace的回调函数,是处理混合占位符最直接、轻量的方式。它既不需要重框架,也能应对默认值和格式化需求,适合绝大多数字符串模板参数替换场景。