JS `Template Literals` 与标签函数:自定义字符串解析与格式化

各位观众,晚上好!今天咱们来聊聊 JavaScript 里一个既强大又灵活的家伙——模板字面量(Template Literals)以及它的小伙伴:标签函数(Tagged Templates)。准备好了吗?Let’s roll!

Part 1: 模板字面量的基本姿势

首先,什么是模板字面量?简单来说,它就是用反引号(`)包裹的字符串。它比普通字符串更强大,允许我们在字符串里嵌入变量,进行多行书写,而无需像以前那样使用各种奇奇怪怪的拼接方法。

const name = "张三";
const age = 30;

// 普通字符串拼接
const greetingOld = "你好," + name + "!你今年 " + age + " 岁了。";

// 模板字面量
const greetingNew = `你好,${name}!你今年 ${age} 岁了。`;

console.log(greetingOld); // 输出: 你好,张三!你今年 30 岁了。
console.log(greetingNew); // 输出: 你好,张三!你今年 30 岁了。

看到了没?${} 这就是魔法!它可以把变量的值塞进字符串里。妈妈再也不用担心我的字符串拼接了!

1.1 优点总结

  • 简洁性: 告别繁琐的 + 号拼接,代码更易读。
  • 可读性: 变量直接嵌入字符串,逻辑一目了然。
  • 多行字符串: 直接换行,无需 n,保持代码格式。
const multiLineString = `
  这是一个
  多行字符串,
  可以直接换行,
  真方便!
`;

console.log(multiLineString);

输出:

  这是一个
  多行字符串,
  可以直接换行,
  真方便!

1.2 表达式嵌入

${} 里不仅仅能放变量,还能放任何有效的 JavaScript 表达式。

const a = 10;
const b = 20;

const result = `a + b = ${a + b}`;

console.log(result); // 输出: a + b = 30

const isEven = num => num % 2 === 0;
const number = 7;

const message = `${number} 是${isEven(number) ? '偶数' : '奇数'}。`;

console.log(message); // 输出: 7 是奇数。

Part 2: 标签函数:模板字面量的超能力

现在,重头戏来了:标签函数。标签函数允许我们自定义如何解析模板字面量。它就像一个拦截器,在模板字面量被解析成字符串之前,先执行我们自定义的函数。

2.1 基本用法

标签函数就是一个普通的函数,但它的调用方式很特别,直接写在模板字面量前面,就像给模板字面量贴了个标签。

function myTag(strings, ...values) {
  console.log("strings:", strings);
  console.log("values:", values);

  return "Hello from myTag!";
}

const name = "李四";
const age = 25;

const taggedString = myTag`你好,${name}!你今年 ${age} 岁了。`;

console.log(taggedString); // 输出: Hello from myTag!

分析一下:

  • myTag 就是我们的标签函数。
  • strings 是一个数组,包含了模板字面量中所有静态字符串部分。 在这个例子中,strings 的值是 ["你好,", "!你今年 ", " 岁了。"]
  • ...values 是一个剩余参数,包含了模板字面量中所有嵌入的表达式的值。 在这个例子中,values 的值是 ["李四", 25]
  • 标签函数的返回值会替换掉整个模板字面量表达式。

2.2 更实际的例子:HTML 转义

一个常见的应用场景是 HTML 转义,防止 XSS 攻击。

function escapeHTML(strings, ...values) {
  let result = "";
  for (let i = 0; i < strings.length; i++) {
    result += strings[i];
    if (i < values.length) {
      result += String(values[i])
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;")
        .replace(/'/g, "'");
    }
  }
  return result;
}

const userInput = "<script>alert('XSS!')</script>";
const safeHTML = escapeHTML`<div>用户输入:${userInput}</div>`;

console.log(safeHTML);
// 输出: <div>用户输入:&lt;script&gt;alert('XSS!')&lt;/script&gt;</div>

在这个例子中,escapeHTML 函数对嵌入的变量 userInput 进行了 HTML 转义,将特殊字符替换成了 HTML 实体,防止了 XSS 攻击。

2.3 高级应用:国际化(i18n)

标签函数还可以用于国际化。我们可以根据用户的语言环境,选择不同的翻译文本。

const translations = {
  en: {
    greeting: "Hello, ${name}! You are ${age} years old."
  },
  zh: {
    greeting: "你好,${name}!你今年 ${age} 岁了。"
  }
};

let currentLocale = 'zh'; //默认中文

function i18n(strings, ...values) {
  const key = strings.join('${}'); // 将字符串数组拼接成一个 key
  const translatedString = translations[currentLocale][key];

  if (!translatedString) {
    console.warn(`Translation missing for key: ${key} in locale: ${currentLocale}`);
    return strings.reduce((acc, str, i) => acc + str + (values[i] || ''), ''); // Fallback to original string
  }

  // 使用正则表达式替换占位符
  return translatedString.replace(/${(w+)}/g, (match, placeholder) => {
    const index = strings.slice(0, strings.indexOf(match.substring(2,match.length-2))).length;
    return values[index] || match; // 如果找不到对应的值,则保留占位符
  });
}

const name = "王五";
const age = 35;

const greeting = i18n`greeting`;

console.log(greeting); // 输出: 你好,王五!你今年 35 岁了。

currentLocale = 'en'; //切换英文
const greetingEn = i18n`greeting`;
console.log(greetingEn); //输出 Hello, ${name}! You are ${age} years old.

这个例子展示了如何使用标签函数实现简单的国际化。 i18n 函数根据 currentLocale 选择不同的翻译文本,并将变量值插入到翻译后的字符串中。

2.4 其他应用场景

  • 格式化数字和日期: 根据不同的地区设置,格式化数字和日期。
  • 代码高亮: 对代码进行语法高亮显示。
  • SQL 查询构建: 安全地构建 SQL 查询语句,防止 SQL 注入。
  • 自定义 DSL (领域特定语言): 创建自定义的语言,用于描述特定领域的问题。

Part 3: 深入理解标签函数

3.1 strings 数组的特性

strings 数组有一个特殊的属性:它的 raw 属性。 strings.raw 也是一个数组,包含了原始的、未经处理的字符串。

function showRaw(strings, ...values) {
  console.log("strings:", strings);
  console.log("strings.raw:", strings.raw);
  console.log("values:", values);
}

const str = showRaw`HellonWorld`;

输出:

strings: ["Hello
World"]
strings.raw: ["HellonWorld"]
values: []

可以看到,strings 中的换行符 n 被解释成了真正的换行,而 strings.raw 中的 n 仍然是字面上的 n。 这在处理特殊字符时非常有用。

3.2 标签函数的返回值

标签函数的返回值会替换掉整个模板字面量表达式。 如果我们不返回任何值,模板字面量表达式的结果就是 undefined

function doNothing(strings, ...values) {
  // 什么也不做
}

const result = doNothing`Hello, world!`;

console.log(result); // 输出: undefined

3.3 标签函数的参数数量

标签函数的第一个参数永远是 strings 数组,后面的参数是嵌入的表达式的值。 如果没有嵌入任何表达式,values 就是一个空数组。

function noValues(strings) {
  console.log("strings:", strings);
}

noValues`This is a simple string.`;

输出:

strings: ["This is a simple string."]

Part 4: 代码示例

为了更好地理解,我们再来几个更具体的例子:

4.1 数字格式化

function formatNumber(strings, ...values) {
  const number = values[0];
  const precision = values[1] || 2; // 默认保留两位小数

  const formattedNumber = number.toFixed(precision);
  return formattedNumber;
}

const price = 1234.5678;
const formattedPrice = formatNumber`价格:${price} 保留 ${3} 位小数`;

console.log(formattedPrice); // 输出: 价格:1234.568

4.2 SQL 注入防御

function safeSQL(strings, ...values) {
  let result = "";
  for (let i = 0; i < strings.length; i++) {
    result += strings[i];
    if (i < values.length) {
      // 对用户输入进行转义,防止 SQL 注入
      result += escapeSQL(values[i]);
    }
  }
  return result;

  function escapeSQL(value) {
    //  简化的 SQL 转义示例,实际应用中需要更完善的实现
    return String(value).replace(/'/g, "''");
  }
}

const tableName = "users";
const columnName = "name";
const userInput = "Robert'); DROP TABLE students;--"; // 恶意输入

const sql = safeSQL`SELECT * FROM ${tableName} WHERE ${columnName} = '${userInput}'`;

console.log(sql);
// 输出: SELECT * FROM users WHERE name = 'Robert''); DROP TABLE students;--'
//  注意:此处只是简单的演示,实际的 SQL 注入防御需要更复杂的处理

Part 5: 总结与最佳实践

模板字面量和标签函数是 JavaScript 中强大的字符串处理工具,可以大大提高代码的可读性和可维护性。

5.1 优点回顾

  • 简洁性: 使用 ${} 嵌入变量,避免繁琐的字符串拼接。
  • 可读性: 代码逻辑清晰,易于理解。
  • 灵活性: 标签函数允许自定义字符串解析和格式化。
  • 安全性: 可以用于 HTML 转义和 SQL 注入防御。

5.2 最佳实践

  • 合理使用模板字面量: 在需要嵌入变量或多行字符串时,优先使用模板字面量。
  • 谨慎使用标签函数: 只有在需要自定义字符串处理逻辑时,才使用标签函数。
  • 注意安全性: 在使用标签函数处理用户输入时,务必进行安全检查和转义,防止 XSS 攻击和 SQL 注入。
  • 代码可读性: 编写清晰易懂的标签函数,避免过度复杂的逻辑。
  • 性能考虑: 复杂的标签函数可能会影响性能,需要进行测试和优化。

5.3 局限性

  • 调试困难: 标签函数内部的错误可能不太容易调试。
  • 过度使用: 不要为了使用而使用,简单的字符串拼接可能更合适。

5.4 表格总结

特性 模板字面量 标签函数
语法 使用反引号 () 包裹 | 函数名 + 反引号 () 包裹的模板字面量
功能 嵌入变量、多行字符串 自定义字符串解析和格式化
参数 N/A strings 数组 + 嵌入表达式的值
返回值 解析后的字符串 自定义的返回值,替换整个模板字面量表达式
应用场景 简单的字符串拼接、多行文本 HTML 转义、国际化、数字格式化、SQL 注入防御、代码高亮等
优点 简洁、易读 灵活、可扩展、安全
缺点 功能有限 可能增加代码复杂性、性能问题
是否必须 否,可以使用普通字符串 否,只有在需要自定义字符串处理时才使用
安全性 默认不进行任何安全处理 可以通过自定义函数进行安全处理,例如 HTML 转义
使用建议 在需要嵌入变量或多行字符串时使用 在需要自定义字符串解析和格式化,或者需要进行安全处理时使用

好了,今天的分享就到这里。希望大家对模板字面量和标签函数有了更深入的了解。 记住,技术是为解决问题而生的,选择合适的工具,才能事半功倍! 感谢大家的观看,下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注