实现模板字符串解析功能

手写实现类似 ES6 模板字符串的解析功能,支持变量插值和表达式计算

问题

实现一个函数,能够解析模板字符串,将其中的变量占位符替换为实际的值。类似于 ES6 的模板字符串功能,但需要手动实现解析逻辑。

例如:将 "Hello, ${name}! You are ${age} years old." 这样的字符串,根据提供的数据对象解析成 "Hello, Tom! You are 18 years old."

解答

/**
 * 模板字符串解析函数
 * @param {string} template - 模板字符串
 * @param {object} data - 数据对象
 * @returns {string} 解析后的字符串
 */
function parseTemplate(template, data) {
  // 使用正则表达式匹配 ${...} 格式的占位符
  return template.replace(/\$\{([^}]+)\}/g, (match, key) => {
    // key 是捕获组中的内容,即 ${} 中间的部分
    const trimmedKey = key.trim();
    
    // 支持表达式计算
    try {
      // 创建一个函数来安全地执行表达式
      const func = new Function(...Object.keys(data), `return ${trimmedKey}`);
      const result = func(...Object.values(data));
      
      // 如果结果是 undefined 或 null,返回空字符串
      return result !== undefined && result !== null ? result : '';
    } catch (e) {
      // 如果表达式执行失败,尝试直接取值
      return data[trimmedKey] !== undefined ? data[trimmedKey] : match;
    }
  });
}

/**
 * 简化版本:仅支持简单的属性访问(包括嵌套属性)
 * @param {string} template - 模板字符串
 * @param {object} data - 数据对象
 * @returns {string} 解析后的字符串
 */
function parseTemplateSimple(template, data) {
  return template.replace(/\$\{([^}]+)\}/g, (match, key) => {
    const trimmedKey = key.trim();
    
    // 支持嵌套属性访问,如 user.name
    const value = trimmedKey.split('.').reduce((obj, prop) => {
      return obj && obj[prop] !== undefined ? obj[prop] : undefined;
    }, data);
    
    return value !== undefined && value !== null ? value : '';
  });
}

/**
 * 增强版本:支持默认值
 * @param {string} template - 模板字符串
 * @param {object} data - 数据对象
 * @param {string} defaultValue - 默认值
 * @returns {string} 解析后的字符串
 */
function parseTemplateWithDefault(template, data, defaultValue = '') {
  return template.replace(/\$\{([^}]+)\}/g, (match, key) => {
    const trimmedKey = key.trim();
    
    // 支持默认值语法:${name || 'Anonymous'}
    if (trimmedKey.includes('||')) {
      const [varName, defVal] = trimmedKey.split('||').map(s => s.trim());
      const value = varName.split('.').reduce((obj, prop) => {
        return obj && obj[prop] !== undefined ? obj[prop] : undefined;
      }, data);
      
      return value !== undefined && value !== null && value !== '' 
        ? value 
        : defVal.replace(/['"]/g, '');
    }
    
    const value = trimmedKey.split('.').reduce((obj, prop) => {
      return obj && obj[prop] !== undefined ? obj[prop] : undefined;
    }, data);
    
    return value !== undefined && value !== null ? value : defaultValue;
  });
}

使用示例

// 示例1:基本使用
const template1 = "Hello, ${name}! You are ${age} years old.";
const data1 = { name: "Tom", age: 18 };
console.log(parseTemplate(template1, data1));
// 输出: "Hello, Tom! You are 18 years old."

// 示例2:支持表达式计算
const template2 = "The sum is ${a + b}, and the product is ${a * b}.";
const data2 = { a: 5, b: 3 };
console.log(parseTemplate(template2, data2));
// 输出: "The sum is 8, and the product is 15."

// 示例3:支持嵌套属性访问
const template3 = "User: ${user.name}, Email: ${user.email}";
const data3 = {
  user: {
    name: "Alice",
    email: "alice@example.com"
  }
};
console.log(parseTemplateSimple(template3, data3));
// 输出: "User: Alice, Email: alice@example.com"

// 示例4:支持默认值
const template4 = "Welcome, ${username || 'Guest'}!";
const data4 = { username: "" };
console.log(parseTemplateWithDefault(template4, data4));
// 输出: "Welcome, Guest!"

// 示例5:处理不存在的变量
const template5 = "Hello, ${name}! Your score is ${score}.";
const data5 = { name: "Bob" };
console.log(parseTemplate(template5, data5));
// 输出: "Hello, Bob! Your score is ."

// 示例6:复杂表达式
const template6 = "Price: $${price}, Tax: $${price * 0.1}, Total: $${price * 1.1}";
const data6 = { price: 100 };
console.log(parseTemplate(template6, data6));
// 输出: "Price: $100, Tax: $10, Total: $110"

关键点

  • 正则表达式匹配:使用 /\$\{([^}]+)\}/g 匹配所有 ${...} 格式的占位符,其中 ([^}]+) 捕获大括号内的内容

  • replace 回调函数replace 方法的第二个参数可以是函数,接收匹配到的字符串和捕获组作为参数

  • 动态表达式执行:使用 new Function() 可以动态执行字符串形式的 JavaScript 表达式,实现计算功能

  • 嵌套属性访问:使用 reduce 方法遍历属性路径,支持 user.name 这样的嵌套访问

  • 异常处理:使用 try-catch 捕获表达式执行错误,提供降级方案

  • 边界情况处理:对 undefinednull、空字符串等特殊值进行判断和处理

  • 安全性考虑:在生产环境中使用 new Function() 需要注意安全风险,应该对输入进行验证和过滤,或使用更安全的解析方式