一、函数定义
函数是由一连串的子程序(语句的集合)所组成的,可以被外部程序调用,向函数传递参数之后,函数可以返回一定的值。
通常情况下,TypeScript 代码是自上而下执行的,不过函数体内部的代码则不是这样。如果只是对函数进行了声明,其中的代码并不会执行,只有在调用函数时才会执行函数体内部的代码。
二、函数格式类型
1. 函数定义类型:
function add(arg1: number, arg2: number): number {return x + y;
}
// 或者
const add = (arg1: number, arg2: number): number => {return x + y;
};
上面例子中参数 arg1 和 arg2 都是数值类型,最后通过相加得到的结果也是数值类型。
如果在这里省略参数的类型,TypeScript 会默认这个参数是 any 类型;如果省略返回值的类型,如果函数无返回值,那么 TypeScript 会默认函数返回值是 void 类型;如果函数有返回值,那么 TypeScript 会根据我们定义的逻辑推断出返回类型。
2. 完整的函数类型
一个函数的定义包括函数名、参数、逻辑和返回值。我们为一个函数定义类型时,完整的定义应该包括参数类型和返回值类型。上面的例子中,我们都是在定义函数的指定参数类型和返回值类型。接下来我们看下,如何定义一个完整的函数类型,以及用这个函数类型来规定一个函数定义时参数和返回值需要符合的类型。先来看例子然后再进行解释:
let add: (x: number, y: number) => number;
add = (arg1: number, arg2: number): number => arg1 + arg2;
add = (arg1: string, arg2: string): string => arg1 + arg2; // error
上面这个例子中,我们首先定义了一个变量 add,给它指定了函数类型,也就是(x: number, y: number) => number,这个函数类型包含参数和返回值的类型。然后我们给 add 赋了一个实际的函数,这个函数参数类型和返回类型都和函数类型中定义的一致,所以可以赋值。后面我们又给它赋了一个新函数,而这个函数的参数类型和返回值类型都是 string 类型,这时就会报如下错误:
函数中如果使用了函数体之外定义的变量,这个变量的类型是不体现在函数类型定义的。
3. 使用接口定义函数类型
interface Add {(x: number, y: number): number;
}
let add: Add = (arg1: string, arg2: string): string => arg1 + arg2; // error 不能将类型“(arg1: string, arg2: string) => string”分配给类型“Add”
4. 使用类型别名
type Add = (x: number, y: number) => number;
let add: Add = (arg1: string, arg2: string): string => arg1 + arg2; // error 不能将类型“(arg1: string, arg2: string) => string”分配给类型“Add”
使用type关键字可以为原始值、联合类型、元组以及任何我们定义的类型起一个别名。上面定义了 Add 这个别名后,Add就成为了一个和(x: number, y: number) => number一致的类型定义。例子中定义了Add类型,指定add类型为Add,但是给add赋的值并不满足Add类型要求,所以报错。
三、参数
1. 必选参数
必选参数:在调用函数的时候,必须要传入的参数,参数列表里边的参数默认就是必选参数,只要在声明的时候写了参数,在传递的时候,就必须传入参数,而且,实参与形参的数量与类型要一致。
type Add = (x: number, y: number) => number;
let add: Add = (arg1: string, arg2: string): string => arg1 + arg2;add(1, 2); // right
add(1, 2, 3); // error 应有 2 个参数,但获得 3 个
add(1); // error 应有 2 个参数,但获得 1 个
2. 可选参数
可选参数:为了解决在函数传参的时候,某些参数可以不用传递,我们就需要可选参数了
function getInfo(name: string, age?: number): string {return `${name} --- ${age}`;
}console.log(getInfo("张三", 28)); // 正确
console.log(getInfo("张三")); // 正确
console.log(getInfo(28)); // 错误
type Add = (x?: number, y: number) => number; // error 必选参数不能位于可选参数后。
注意:可选参数必须配置到参数的最后面。
3. 默认参数
默认参数:为了解决在函数传参的时候,某些参数可以不用传递,但是我们又需要该参数的值,这时候我们就需要给这个参数设定一个默认值也叫初始化值,就得用到默认参数了。
function getInfo(name: string, age: number = 20): string {return `${name} --- ${age}`;
}console.log(getInfo("张三", 28)); // 正确
console.log(getInfo("张三")); // 正确
console.log(getInfo(28)); // 错误
注意:可选参数不能够进行初始化值的设定。
4. 剩余参数
剩余参数:在参数的类型确定而参数个数不确定的情况时,我们需要用到剩余参数,它使用 … 将接收到的参数传到一个指定类型的数组中。
function sum(...result: number[]): number {let sum = 0;for (let i = 0; i < result.length; i++) {sum += result[i];}return sum;
}console.log(sum(1, 2, 3, 4, 5, 6));
function sum(init: number, ...result: number[]): number {let sum = init;for (let i = 0; i < result.length; i++) {sum += result[i];}return sum;
}console.log(sum(100, 1, 2, 3, 4, 5, 6));
注意:剩余参数必须配置到参数的最后面。
四、函数重载
1. 函数签名
函数签名主要定义了参数及参数类型,返回值及返回值类型。函数签名不同,函数会做出不同的处理。
2. 构造器重载
举个例子,声明一个类Course,里面写一个begin的方法,我们调用 begin时传入不同参数类型已经参数个数,begin方法会做出不同处理,那么怎么实现呢?4个重载签名和1个实现签名具体如下:
type Combinable = number | string;
class Course {//定义重载签名begin(name: number, score: number): string;begin(name: string, score: string): string;begin(name: string, score: number): string;begin(name: number, score: string): string;//定义实现签名begin(name: Combinable, score: Combinable) {if (typeof name === 'string' || typeof score === 'string') {return 'student:' + name + ':' + score; }}
}const course = new Course();
console.log(course.begin(111, 5)); //没有输出
console.log(course.begin('zhangsan', 5)); //student:zhangsan:5
console.log(course.begin(5, 'zhangsan')); //student:5:zhangsan
3. 联合类型函数重载
声明一个函数uniteFunctionOverloading,参数类型为联合类型,返回值也是联合类型,但是如下代码却报错了。
function uniteFunctionOverloading(x: number | string): number | string {if (typeof x === 'number') {return x;} else {return x+'是字符串';}
}
uniteFunctionOverloading(1).toFixed(1); // 报错 类型“string | number”上不存在属性“toFixed”。类型“string”上不存在属性“toFixed”
我们可以可以根据传参的类型和函数返回值声明多个同名的函数,只是类型和返回值不同而已。
function uniteFunctionOverloading(x: number): number;
function uniteFunctionOverloading(x: string): string;
function uniteFunctionOverloading(x: number | string): number | string {if (typeof x === 'number') {return x;} else {return x+'是字符串';}
}
uniteFunctionOverloading(1).toFixed(1);
这样就不会报错了,可以利用重载将多种情况声明这样就可推导类型更细致,因为已经识别到uniteFunctionOverloading(1)的返回值是number类型。
五、拓展JS中函数重载
1. 利用arguments参数
var arr = [1,2,3,4,5];
//注意:这里不能写成箭头函数,否则this指向的是window对象
Array.prototype.search = function() {var len = arguments.length;switch(len){case 0:return this;case 1:return `${arguments[0]}`;case 2:return `${arguments[0]},${arguments[1]}`;}
}
console.log(arr.search()) //[1,2,3,4,5]
console.log(arr.search(1)) //1
console.log(arr.search(1,2)) //1,2
2. 利用闭包和arguments
function addMethod (obj, name, fn) {var old = obj[name];obj[name] = function () {if (fn.length === arguments.length) {return fn.apply(this, arguments)} else if (typeof old === 'function') {return old.apply(this, arguments)}}}var person = {name: 'zhangsan'}addMethod(person, 'getName', function () {console.log(this.name + '---->' + 'getName1')})addMethod(person, 'getName', function (str) {console.log(this.name + '---->' + str)})addMethod(person, 'getName', function (a, b) {console.log(this.name + '---->' + (a + b))})person.getName() person.getName('zhangsan')person.getName(10, 20)