# 对象类型-函数
本章讲解对象类型中的函数。了解如何限制函数类型。在开始之前,建议阅读 MDN 函数 (opens new window) 理解常见的函数类型。
# 函数申明的类型限制
在创建函数时,可以对参数类型和函数返回值进行注解。
// 定义一个函数,限制输入参数必须为 number 且返回值为 number
function add(a: number, b: number): number {
return a + b;
}
// 合法
add(1, 2);
// 报错,缺少参数
add(1);
// 报错,返回值为 number 类型不能赋值给 string 类型的变量
let str: string = add(1, 2);
函数申明中的类型限制定义了函数的调用的规则,利用静态类型检查 ,避免了函数参数缺失或输入类型不匹配等常见问题。
# 函数类型
采用 函数类型字面量(function type literal) (参数1:类型,参数2:类型,...) => 返回类型 的方式申明函数类型,该申明语法和箭头函数类似。
// 定义变量 func 必须为函数类型,函数没有输入参数且函数无返回值
let func: () => void;
// 定义变量 func1 必须为函数类型,函数的必须有两个输入参数且类型为 number ,返回类型也为 number
// 注意参数名没有限制,保持语义化即可
let func1: (a: number, b: number) => number;
// 合法符合限定额函数类型
func = () => {};
// 合法
func1 = (a, b) => a + b;
// 调用合法
func1(1, 2);
// 报错,少输了一个参数
func1(1);
// 报错,参数 2 类型不对
func1(1, '1');
// 报错,返回值为 number 不能赋值给 string 类型的变量
let str: string = func1(1, 2);
示例中虽然箭头函数在申明时并没有对类型和返回值做出规定,但基于 类型推断任然检查了函数的调用错误。
注意区分函数申明的类型限制和函数类型这两个概念。
- 函数申明的类型限制 用于创建函数时,通过申明参数和返回值的类型,避免函数的调用错误
- 函数类型 是一个类型不产生运行时代码,用于限定参数为特定的函数类型
由于函数也是对象,也可采用**对象类型字面量(object type literal)**可以采用 { (参数1:类型,参数2:类型,...):类型} 的方式注解函数,也可通过类型别名复用函数类型的定义
// 定义一个无输入参数和返回的函数类型 Func
type Func = () => void;
// 采用对象方式申明变量 foo 只接受一个输入参数为 number,且返回值为 number 的函数
let foo: { (a: number): number };
// 采用类型别名复用上述函数定义
type Func1 = { (a: number): number };
也支持使用 Interface 语法为函数类型创建一个语义化名称。
// interface 定义一个搜索函数类型
interface SearchFunc {
(source: string, subString: string): boolean;
}
// 定义变量类型只接受搜索函数
let searchStr: SearchFunc;
searchStr = function(source: string, subString: string) {
let result = source.search(subString);
return result > -1;
};
console.log(searchStr('abcd', 'a'));
# 可选参数
有时我们期望函数的参数是可选的,通过 参数?:类型 的方式表示可选。
这和对象可选属性的定义类似。你也可以在函数类型中采用此语法。
// 定义函数第二个参数为可选
function buildName(firstName: string, lastName?: string) {
if (lastName) return firstName + ' ' + lastName;
else return firstName;
}
// 合法
let result1 = buildName('Bob');
// 合法
let result3 = buildName('Bob', 'Adams');
// 报错多了一个输入参数
let result2 = buildName('Bob', 'Adams', 'Sr.');
// 定义一个函数类型,参数 a,b 必须为 number 类型,参数 b 可选,返回值为 number
type Foo = (a: number, b?: number) => number;
//报错, 注意可选参数必须放在最后
type Foo1 = (a?: number, b: number) => number;
# 剩余参数
ES6 中新增了 剩余参数 (opens new window) 采用 ...参数名 表示额外传入函数的参数,
由于剩余为数组格式,我们可以采用 ...参数名:类型[] 限定剩余参数为特定数组类型。[] 符号表示数组类型,后续会详细讲解。
// 定义剩余为字符串数组
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + ' ' + restOfName.join(' ');
}
let employeeName: string;
// 只要剩余参数为字符串类型,不限制掺入的参数数量
employeeName = buildName('Joseph');
employeeName = buildName('Joseph', 'Samuel');
employeeName = buildName('Joseph', 'Samuel', 'Lucas', 'MacKinzie');
# 剩余参数和元祖类型
剩余参数也可为元祖类型,效果同将元祖元素分别展开赋值给参数相同。
// 定义剩余参数为元祖
// 效果类似 arg0:string,arg1:number,arg2:string,arg3:boolean
// 所以函数必须接受 4 个参数
function fooFunc(arg0: string, ...args: [number, string, boolean]) {}
fooFunc('foo', 12, 'foo', true);
// 报错,缺少一个参数
fooFunc('foo', 12, 'foo');
你也可以结合元祖的可选元素,来表示函数参数可选。
// 定义剩余参数为元祖
// 效果类似 arg0:string,arg1:number,arg2?:string,arg3?:boolean
// 所以函数必须接受 4 个参数
function barFunc(arg0: string, ...args: [number, string?, boolean?]) {}
// 合法
barFunc('foo', 12, 'foo', true);
barFunc('foo', 12, 'foo');
barFunc('foo', 12);
// 报错,缺少一个参数
barFunc('foo');
# 展开赋值
注意在使用 展开语法 (opens new window) 给函数赋值时,必须显示申明赋值类型为参数类型组成的元祖,否则会报错。
function funBar(str: string, num: number) {}
// 报错, TypeScript 需要显示申明输入类型为 [string,number] 的元祖才可执行扩展
funBar(...['foo', 1]);
// 采用类型断言,申明扩展数组为 [string,number] 的元祖
funBar(...(<[string, number]>['foo', 1]));
// 效果同上,使用 as 语法
funBar(...(['bar', 1] as [string, number]));
# 典型的函数类型
JavaScript 的函数及其灵活,由于参数和返回值也可为函数,存在一些典型的函数类型。举例如下
# 回调函数
浏览器端使用 addEventListener (opens new window) 绑定事件时,支持传入一个监听器函数作为回调,回调接收事件对象作为参数。
node 端异步调用接口时,也支持传入回调,略有区别的是回调的第一个参数为错误信息,第二参数为实际结果。 node api 文档 (opens new window) 也强调了这种规范。
在查看示例之前建议你自行实现上述定义
// 浏览器端 addEventListener 的类型函数申明示例
// 定义事件回调类型,参数为对象,无返回值
type EventCallback = (event: object) => void;
// 定义绑定监听器的函数,参数为事件名和回调函数,无返回值
type addEventListener = (eventName: string, cb: EventCallback) => void;
// node 回调模式的申明示例
// 申明函数类型第一个参数为错误对象,第二个参数为实际数据,无返回值
type NodeCallBack = (err: Error, data: Object) => void;
// 申明一个 nodeApi 函数,输入为数据和回调,无返回值
type nodeApi = (data: Object, cb: NodeCallBack) => void;
# 函数重载
参考 wiki (opens new window) 函数重载 是一个语言特性,指语言支持创建多个同名但具有不同实现的函数,在运行时根据输入参数个数或输入类型的不同,执行对应实现。
JavaScript 不具有此语言特性,但是存在一个函数,根据不同输入参数执行不同逻辑的情况,一个典型示例就是科里化,科里化是函数式编程中一个的模式,若要学习函数式编程推荐 github 开源书籍 函数式编程 (opens new window)。
这里简化示例,假设定义一个求和函数,输入两个参数返回和,输入一个参数则返回一个函数,
返回的函数支持在传入一个参数来计算结果。例如 sum(1,2) =3,f=sum(1);f(2) = 3。则我们可以通过多次声明一个函数来实现上述定义
// 定义一个 CurrySum
type CurrySum = {
(): any; // 无参数则返回任意类型
(a: number): any; // 单一参数返回任意类型
(a: number, b: number): any; // 两个参数返回任意类型
};
function sum(a?: number, b?: number) {
if (a !== undefined && b !== undefined) {
return a + b;
} else if (a !== undefined) {
return function(x: number) {
return x + a;
};
} else {
return sum;
}
}
// 合法,符合函数类型
let a: CurrySum = sum;
console.log(a(1)(2)); // 返回 3
console.log(a(1, 2)); // 返回 3
# 构造函数
构造函数多用 new 进行初始化,你可以在函数类型中申明 new 表示函数必须为构造器。
// 工厂方法限制传入参数必须是构造函数
function FactoryCreate(ctor: new () => void): any {
return new ctor();
}
// 类,默认为构造函数
class Ctor1 {}
// 申明的函数,默认无法采用 new 调用
function Ctor2() {}
// 合法
let ctor1 = FactoryCreate(Ctor1);
console.log(ctor1);
// 非法,函数默认不支持 new 的调用
let ctor2 = FactoryCreate(Ctor2);
console.log(ctor2);
对于类默认支持采用 new 调用,为了让函数支持构造调用,可利用函数重载结合类型断言显示申明
普通函数支持构调用。
// 工厂方法限制传入参数必须是构造函数
function FactoryCreate(ctor: new () => void): any {
return new ctor();
}
// 申明函数类型可以采用 new 调用或直接调用
interface ConstructFunction {
new (): void;
(): void;
}
function Ctor() {}
// 合法, 采用类型断言和重载申明 Ctor 为构造函数
let ctor = FactoryCreate(Ctor as ConstructFunction);
console.log(ctor);
# 总结
本章学习了如何对函数进行类型限制,知识点如下
- 函数申明时可以类型注释限制函数参数和返回值,用来申明函数的调用方式
- 有两种定义函数类型的语法
- 采用类似箭头函数的 函数类型字面量
(参数1:类型,参数2:类型,...) => 返回值类型 - 采用 对象类型字面量
{ (参数1:类型,参数2:类型,...):类型 }
- 采用类似箭头函数的 函数类型字面量
- 也可采用 type 创建函数类型别名,或使用 Interface 定义函数类型名称
- 采用
参数?:类型表示可选参数 - 采用
..args:类型[]说明剩余参数的数组类型 - 通过类型别名组合多个类型申明回调模式或函数重载
← 对象类型-数组和元祖 对象类型-类 →