在 TypeScript 中通过调用签名构建简易 Mini Day.js 克隆与 Logger 中间件
Source: Dev.to
(请提供您希望翻译的完整文本内容,我将按照要求保留链接、格式和代码块,仅翻译正文部分。)
首先,了解函数是对象
你应该知道 typeof 函数原型的结果是 object —— 我指的是原型,而不是 typeof function 本身。
如果你对原型不熟悉,我强烈建议先阅读相关内容再继续。
我们想要解决的问题
如何对函数进行配置,然后使用这些配置来执行它?
这在某种程度上类似于 闭包,但闭包是通过变量作用域封装状态的。使用调用签名时,状态会暴露在外部——这意味着我们 可以在需要时修改属性。
你可能会在像 dayjs 这样的库中或实现日志中间件时遇到这种模式。我们该如何在 TypeScript 中正确定义它?
Source:
TypeScript 中的调用签名
在 JavaScript 中,函数除了可以被调用外,还可以拥有属性。然而,典型的函数类型语法并不允许声明属性。如果我们想描述一个既可调用又带有属性的对象,可以使用 对象类型中的调用签名。
调用签名类似于闭包,但关键区别在于状态位于 函数外部,因此可以被修改。
示例 1 — Mini Day.js 克隆
type DayJS = {
timezone: string;
description: string;
(date: string | number | Date): string;
};
const dayjs = function (date: string | number | Date) {
const tz = dayjs.timezone;
const formatter = new Intl.DateTimeFormat("en-US", {
timeZone: tz,
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
});
return formatter.format(new Date(date));
} as DayJS;
dayjs.timezone = "Asia/Ho_Chi_Minh";
dayjs.description = "Mini DayJS clone";
console.log(dayjs("2026-02-04"));
// 02/04/2026, 07:00:00 AM
这里,dayjs 同时具备:
- 像函数一样可调用
- 通过属性进行配置
这正是 调用签名 所实现的功能。
示例 2 — 日志中间件
现在实现一个可配置属性的日志中间件,例如:
isEnabled(基于生产环境)- 日志类型(
warning、error、info) - 消息负载
type LoggerData = { message: string; metadata?: unknown };
type LoggerType = "warning" | "error" | "info";
type LoggerMiddleware = {
isEnabled: boolean;
type: LoggerType;
(data: LoggerData): void;
};
const initializedSomeService = () => {
const warningLogger: LoggerMiddleware = function (data) {
if (!warningLogger.isEnabled) return;
console.warn(`TYPE:${warningLogger.type}`, { data });
};
// 配置属性
warningLogger.isEnabled = process.env.NODE_ENV === "production";
warningLogger.type = "warning";
// 像函数一样执行
warningLogger({
message: "This is a warning message",
metadata: { id: "ab85592d-b7cf-4623-9884-aa70f40814f7" },
});
};
initializedSomeService();
结果:
TYPE:warning {
data: {
message: 'This is a warning message',
metadata: { id: 'ab85592d-b7cf-4623-9884-aa70f40814f7' }
}
}
何时应该使用调用签名?
当你需要以下特性的东西时使用它们:
- ✅ 像函数一样可调用
- ✅ 可通过属性进行配置
- ✅ 强类型
典型的实际使用场景:
- 日志工具
- 分析追踪器
- 功能标志执行器
- SDK 风格的 API(例如,像 dayjs 这样的库)
闭包 vs 调用签名(快速洞察)
| 闭包 | 调用签名 | |
|---|---|---|
| 状态处理 | 封装状态 | 暴露状态 |
| 可变性 | 防止变异更安全 | 可变 |
| 风格 | 更函数式 | 更库式 |
| 常见模式 | Hook(钩子) | SDK 设计 |
两者各有优劣——根据你的设计目标选择。
- 如果你想要不可变性 → 更倾向于使用闭包。
- 如果你想要可配置性 → 调用签名更出色。
最后思考
调用签名是 TypeScript 的一个小特性,但一旦你理解了它们,你会在许多库中开始注意到这种模式。它们提供了一种强大的方式来设计符合 JavaScript 自然感觉的 API —— 可配置、可调用且强类型。
绝对值得加入你的 TypeScript 工具箱 🚀