You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让模板替换函数实现递归?是否会出现无限递归?

支持数组递归渲染的模板替换函数实现

实现逻辑

要让模板替换函数支持数组递归渲染,核心是在匹配到数组类型的变量时,遍历数组每一项,用当前项作为数据源递归调用模板函数,最后把所有递归结果拼接起来替换原占位符。具体步骤:

  1. 用正则匹配所有%variable%(支持嵌套属性如%user.name%)格式的占位符
  2. 对每个占位符对应的变量,从数据源中取值:
    • 如果是数组:遍历数组元素,对每个元素递归执行模板替换,将所有结果拼接成字符串
    • 如果是普通值(字符串/数字等):直接替换占位符
    • 如果是嵌套对象:解析嵌套属性路径,取出对应值替换
  3. 加入循环引用防护,避免无限递归

无限递归风险

正常使用下不会触发无限递归,但如果数据源存在循环引用(比如数组元素引用了父数组,或对象引用链形成闭环),就会导致递归无法终止。解决办法是在递归过程中记录已处理过的对象/数组,遇到重复引用时直接跳过。

代码实现

function renderTemplate(templateStr, data, processed = new WeakSet()) {
  // 匹配所有%xxx%或%xxx.yyy%格式的占位符
  return templateStr.replace(/%([\w.]+)%/g, (match, key) => {
    // 解析嵌套属性路径
    let value = data;
    const keys = key.split('.');
    for (const k of keys) {
      value = value?.[k];
      if (value === undefined) break;
    }
    if (value === undefined) return match; // 找不到值则保留原占位符

    // 处理数组:递归渲染每个元素后拼接
    if (Array.isArray(value)) {
      if (processed.has(value)) return ''; // 跳过已处理的数组,防止循环引用
      processed.add(value);
      return value.map(item => renderTemplate(templateStr, item, processed)).join('');
    }

    // 处理对象:防止循环引用,这里只处理非数组对象
    if (typeof value === 'object' && value !== null) {
      if (processed.has(value)) return '';
      processed.add(value);
      return String(value);
    }

    // 普通值直接转换为字符串
    return String(value);
  });
}

// 实际使用示例
const invoiceMasterTpl = `
<div class="invoice">
  <h3>发票编号:%invoiceId%</h3>
  <div class="item-list">
    %items%
  </div>
</div>
`;

const invoiceItemTpl = `
<div class="item">
  <span>商品:%productName%</span>
  <span>数量:%count%</span>
  <span>单价:%price%</span>
</div>
`;

const invoiceData = {
  invoiceId: 'INV-2024-007',
  items: [
    { productName: '无线耳机', count: 1, price: 299 },
    { productName: '充电底座', count: 2, price: 59 }
  ]
};

// 先渲染所有商品项,再渲染主发票模板
const renderedItems = invoiceData.items.map(item => renderTemplate(invoiceItemTpl, item)).join('');
const finalInvoice = renderTemplate(invoiceMasterTpl, { ...invoiceData, items: renderedItems });

console.log(finalInvoice);

补充说明

  • 示例中先单独渲染商品项模板,再把结果传入主模板。如果想让函数自动识别数组对应的子模板,可以扩展函数参数,比如传入一个模板映射表(如{ items: invoiceItemTpl }),让函数在处理数组时自动使用对应子模板递归
  • 使用WeakSet记录已处理对象,不会影响垃圾回收,是处理循环引用的高效方式

内容的提问来源于stack exchange,提问作者Shawn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 01:27:45