ES6 中函数的双重调用方式:fn() 与 fn\...``

一、概念

在 ES6 中,函数不仅可以通过传统方式 fn() 调用,还可以通过 标签模板(Tagged Template Literals) 的方式 fn...`` 调用。

两种调用方式的核心差异是参数传入的方式:

  • fn():手动传参。
  • fn...``:由 JavaScript 自动将模板字面量分解为 静态字符串数组动态表达式结果,并传递给函数。

二、原理

1. 普通函数调用

javascript 复制代码
function add(a, b) {
  return a + b;
}
console.log(add(2, 3)); // -> 5

👉 参数完全由调用方提供。

2. 标签模板函数调用

javascript 复制代码
function tag(strings, ...values) {
  console.log(strings); // 静态部分数组
  console.log(values);  // 插值表达式的计算结果
}

const name = "Alice";
const age = 20;
tag`Hi, my name is ${name} and next year I will be ${age + 1}`;

执行过程:

  1. 模板字面量 Hi, my name is ${name} and next year I will be ${age + 1}

  2. JS 引擎分割为:

    • strings = ["Hi, my name is ", " and next year I will be ", ""]
    • values = ["Alice", 21]
  3. 调用函数:tag(strings, "Alice", 21)

👉 这说明 模板中的 ${...} 可以是任意合法表达式,不仅仅是变量。


三、对比

特性 fn() 调用 fn...`` 调用
参数来源 显式传入 模板字面量分解自动传入
动态表达式支持 需手动写表达式并传参 模板内部直接写 ${...},自动求值
可读性 适合逻辑函数 更适合描述文本/DSL 场景
典型应用 算法、业务逻辑 SQL 构造、HTML 渲染、国际化、CSS-in-JS

四、实践

示例 1:带表达式的 SQL 构造器

sql 复制代码
function sql(strings, ...values) {
  // 将插值安全转义(演示用,真实情况需更严格处理)
  const safeValues = values.map(v => `'${v}'`);
  // 拼接完整 SQL
  return strings.reduce((result, str, i) => result + str + (safeValues[i] ?? ""), "");
}

const table = "users";
const minAge = 18;
const maxAge = 30;

// 模板中可直接放表达式:范围查询
const query = sql`SELECT * FROM ${table} WHERE age BETWEEN ${minAge} AND ${maxAge + 5}`;
console.log(query);
// 输出: SELECT * FROM 'users' WHERE age BETWEEN '18' AND '35'

👉 注意 ${maxAge + 5} 就是 表达式,计算后再传入函数。


示例 2:国际化 + 表达式

javascript 复制代码
function i18n(strings, ...values) {
  const dict = { "Hello": "你好", "years old": "岁" };
  return strings.reduce((res, str, i) => {
    const translated = dict[str.trim()] || str;
    return res + translated + (values[i] ?? "");
  }, "");
}

const user = { name: "小明", birth: 2000 };
const currentYear = 2025;

// 模板中写表达式:年龄 = 当前年份 - 出生年份
console.log(i18n`Hello ${user.name}, you are ${currentYear - user.birth} years old`);
// 输出: 你好 小明, you are 25 岁

👉 模板里的 ${currentYear - user.birth} 表达式先求值,再传给标签函数。


五、拓展

  1. GraphQL

    javascript 复制代码
    import { gql } from "@apollo/client";
    const query = gql`
      query {
        user(id: ${42}) {
          name
        }
      }
    `;

    👉 gql 就是一个标签函数,接收模板片段并解析为 AST。

  2. CSS-in-JS(styled-components)

    ini 复制代码
    const Button = styled.button`
      background: ${props => props.primary ? "blue" : "gray"};
      color: white;
    `;

    👉 通过 ${表达式} 可以根据组件属性动态生成 CSS。


六、潜在问题

  1. 表达式副作用
    ${func()} 内部函数若有副作用,模板调用时可能引发隐式执行。
  2. 调试困难
    插值表达式复杂时,最终拼接结果可能难以直观理解。
  3. 性能注意
    模板调用每次会构造新数组,若在循环内频繁使用可能带来 GC 压力。

总结

  • fn() 调用:传统传参,适合业务逻辑。
  • fn...`` 调用 :标签模板调用,自动传入 字面量片段数组 + 表达式求值结果 ,不仅能传变量,还能传 任意 JS 表达式
  • 应用场景:SQL 构造、国际化、GraphQL、CSS-in-JS 等。
相关推荐
可乐爱宅着2 小时前
全栈框架next.js入手指南
前端·next.js
你的人类朋友4 小时前
什么是API签名?
前端·后端·安全
会豪6 小时前
Electron-Vite (一)快速构建桌面应用
前端
中微子6 小时前
React 执行阶段与渲染机制详解(基于 React 18+ 官方文档)
前端
唐某人丶6 小时前
教你如何用 JS 实现 Agent 系统(2)—— 开发 ReAct 版本的“深度搜索”
前端·人工智能·aigc
中微子6 小时前
深入剖析 useState产生的 setState的完整执行流程
前端
遂心_6 小时前
JavaScript 函数参数传递机制:一道经典面试题解析
前端·javascript
小徐_23336 小时前
uni-app vue3 也能使用 Echarts?Wot Starter 是这样做的!
前端·uni-app·echarts
RoyLin6 小时前
TypeScript设计模式:适配器模式
前端·后端·node.js