一、什么是函数签名
函数签名(Function Signature) 是函数的"身份证明"——它定义了函数的名称、参数列表、返回值类型以及可能抛出的异常。简单说,函数签名回答了三个问题:
- 函数叫什么?
- 需要传入什么参数?
- 返回什么结果?
// 函数签名:add(a: number, b: number) => number
function add(a, b) {
return a + b;
}签名 = 函数名 + 参数 + 返回值,不包括函数体内部的实现逻辑。
二、JavaScript 中的函数签名组成
2.1 函数名
函数名是签名的标识符,要遵循见名知意原则:
// 好的命名
function getUserById(id) {}
function calculateTotalPrice(items) {}
function formatDate(date, format) {}
// 不好的命名
function fn1() {}
function process(a, b) {}
function doStuff() {}2.2 参数列表
参数列表是函数签名中最重要的部分,包含参数的数量、顺序和类型。
必选参数
function greet(name) {
return `你好,${name}`;
}
greet("张三"); // ✅ "你好,张三"
greet(); // ❌ name 为 undefined,但不会报错默认参数(ES6)
function greet(name = "朋友") {
return `你好,${name}`;
}
greet("张三"); // "你好,张三"
greet(); // "你好,朋友" — 默认值生效默认参数规则:
- 只有传入
undefined或省略时才触发默认值 null不会触发默认值
function test(x = 10) {
console.log(x);
}
test(undefined); // 10(触发默认值)
test(null); // null(不触发默认值)
test(0); // 0(不触发默认值)剩余参数(Rest Parameters)
function sum(...numbers) {
return numbers.reduce((acc, n) => acc + n, 0);
}
sum(1, 2, 3, 4, 5); // 15剩余参数规则:
- 必须是参数列表中的最后一个
- 将其余所有参数收集为一个真数组(不同于
arguments的伪数组)
// ✅ 正确
function log(level, ...messages) {
console.log(`[${level}]`, ...messages);
}
// ❌ 错误:剩余参数必须在最后
function bad(...args, extra) {}2.3 返回值
JavaScript 函数总是有返回值——未显式 return 时返回 undefined:
function noReturn() {
// 没有 return 语句
}
noReturn(); // undefined
function explicitReturn() {
return 42;
}
explicitReturn(); // 42
function earlyReturn(x) {
if (x < 0) return; // 提前返回 undefined
return x * 2;
}三、函数签名与类型(TypeScript)
TypeScript 为函数签名带来了真正的类型约束:
3.1 基础函数签名
// 完整签名
function add(a: number, b: number): number {
return a + b;
}
// 箭头函数签名
const add: (a: number, b: number) => number = (a, b) => a + b;
// 类型别名方式
type AddFn = (a: number, b: number) => number;
const add: AddFn = (a, b) => a + b;3.2 可选参数
function greet(name: string, title?: string): string {
if (title) {
return `尊敬的 ${title}${name}`;
}
return `你好,${name}`;
}3.3 参数解构的签名
// 对象参数签名
function createUser({ name, age, email }: {
name: string;
age: number;
email?: string;
}): User {
return { name, age, email };
}
// 更清晰的方式:用 interface 定义
interface CreateUserParams {
name: string;
age: number;
email?: string;
}
function createUser(params: CreateUserParams): User {
return params;
}四、函数重载
4.1 TypeScript 函数重载
同一个函数名,根据参数类型或数量的不同,执行不同的逻辑:
// 重载签名(多个)
function format(input: string): string;
function format(input: number): string;
function format(input: Date): string;
// 实现签名(一个)
function format(input: string | number | Date): string {
if (typeof input === "string") {
return input.trim();
}
if (typeof input === "number") {
return input.toFixed(2);
}
return input.toISOString().split("T")[0];
}
// 使用
format(" hello "); // "hello"(string 重载)
format(3.14159); // "3.14"(number 重载)
format(new Date()); // "2025-01-05"(Date 重载)4.2 JavaScript 中的"伪重载"
JavaScript 本身不支持重载,但可以通过参数判断模拟:
function format(input) {
if (typeof input === "string") {
return input.trim();
}
if (typeof input === "number") {
return input.toFixed(2);
}
if (input instanceof Date) {
return input.toISOString().split("T")[0];
}
throw new Error("不支持的参数类型");
}五、特殊函数签名
5.1 回调函数签名
回调函数是最常见的函数签名形式之一:
// 数组方法的回调签名
const numbers = [1, 2, 3];
// forEach 回调签名: (value: T, index: number, array: T[]) => void
numbers.forEach((value, index) => {
console.log(index, value);
});
// map 回调签名: (value: T, index: number, array: T[]) => U
const doubled = numbers.map(n => n * 2);
// filter 回调签名: (value: T, index: number, array: T[]) => boolean
const evens = numbers.filter(n => n % 2 === 0);5.2 事件处理函数签名
// DOM 事件处理签名
function handleClick(event: MouseEvent): void {
console.log(event.clientX, event.clientY);
}
// 自定义事件处理
type EventHandler = (event: Event) => void;
// 可选地使用 event 参数
button.addEventListener("click", () => {
console.log("点击了"); // 不需要 event 时可以省略
});5.3 异步函数签名
// async 函数总是返回 Promise
async function fetchData(url: string): Promise<Response> {
const response = await fetch(url);
return response;
}
// Promise 链中的签名
function processData(data: RawData): Promise<ProcessedData> {
return Promise.resolve(transform(data));
}六、高阶函数的签名
6.1 函数作为参数
type TransformFn<T, R> = (value: T, index: number) => R;
function mapArray<T, R>(
arr: T[],
transform: TransformFn<T, R>
): R[] {
const result: R[] = [];
for (let i = 0; i < arr.length; i++) {
result.push(transform(arr[i], i));
}
return result;
}
// 使用
const strings = mapArray([1, 2, 3], n => n.toString());
// strings: string[]6.2 函数作为返回值(柯里化)
// 柯里化加法
function add(a: number): (b: number) => number {
return (b: number) => a + b;
}
// 签名:add(a: number) => (b: number) => number
const add5 = add(5); // add5 的类型: (b: number) => number
console.log(add5(3)); // 8
console.log(add(2)(3)); // 5
// 实际应用:配置函数
function createLogger(prefix: string) {
return (message: string) => {
console.log(`[${prefix}] ${message}`);
};
}
const infoLogger = createLogger("INFO");
const errorLogger = createLogger("ERROR");
infoLogger("系统启动"); // [INFO] 系统启动
errorLogger("连接失败"); // [ERROR] 连接失败七、泛型函数签名
泛型让函数签名更加灵活和类型安全:
// 基础泛型函数
function identity<T>(value: T): T {
return value;
}
// 多个泛型参数
function pair<A, B>(a: A, b: B): [A, B] {
return [a, b];
}
// 泛型约束
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "张三", age: 25 };
getProperty(user, "name"); // string
getProperty(user, "age"); // number
// getProperty(user, "email"); // ❌ 类型错误八、常见误区与最佳实践
8.1 参数数量不匹配
// ❌ 过多或过少的参数不会报错(JS 中)
function log(a, b) {
console.log(a, b);
}
log(1); // a=1, b=undefined
log(1, 2, 3); // a=1, b=2(第三个参数被忽略)8.2 参数顺序优化
// ❌ 不好的顺序:把可选参数放在必选前面
function fetchData(url, timeout = 5000, callback) {}
// ✅ 好的顺序:必选在前,可选在后,回调在最后
function fetchData(url, callback, timeout = 5000) {}8.3 使用单一对象参数
当参数超过 3 个 时,建议改用对象参数:
// ❌ 参数太多,容易搞错顺序
function createUser(name, age, email, phone, address, avatar) {}
createUser("张三", 25, "...", "...", "...", "..."); // 顺序容易乱
// ✅ 使用对象参数
function createUser({ name, age, email, phone, address, avatar }) {}
createUser({
name: "张三",
age: 25,
email: "zhangsan@example.com",
phone: "13800138000",
address: "北京市",
avatar: "/avatar.jpg"
});8.4 签名设计原则
| 原则 | 说明 |
|---|---|
| 单一职责 | 一个函数只做一件事 |
| 参数最少化 | 能传 2 个就不要传 5 个 |
| 可预测性 | 同样的输入总是同样的输出 |
| 明确意图 | 参数名和类型说明用途 |
| 优雅降级 | 可选参数有合理的默认值 |
// 这些签名一眼就能看出用途
// Array.map(callbackfn: (value: T, index: number) => U): U[]
// String.slice(start: number, end?: number): string
// fetch(url: string, init?: RequestInit): Promise<Response>九、总结
函数签名是 JavaScript 开发者每天都要打交道但又容易忽视的基础知识:
- 签名三要素:函数名 + 参数 + 返回值
- ES6+ 特性:默认参数、剩余参数、解构参数让签名更灵活
- TypeScript 加持:类型注解让函数签名变成可执行的文档
- 设计原则:参数少、顺序合理、可预测、用对象替代长参数列表
熟练掌握函数签名,写出的代码不仅更清晰,也更容易被同事理解和维护。