Typescript入门-函数讲解
- Function 类型
- 箭头函数
- 可选参数
- 参数默认值
- 参数解构
- rest参数
- readonly 只读参数
- void 类型
- 构造函数
函数的类型声明,需要在声明函数时,给出 参数的类型和 返回值的类型。
function hello(txt: string): void {console.log("hello " + txt);
}
上面示例中,函数 hello() 在声明时,需要给出参数 txt 的类型(string),以及返回值的类型(void
),后者写在参数列表的圆括号后面。void
类型表示没有返回值,详见后文。
如果不指定参数类型(比如上例不写txt的类型),TypeScript 就会推断参数类型,如果缺乏足够信息,就会推断该参数的类型为any。
返回值的类型通常可以不写,因为 TypeScript 自己会推断出来。
如果不指定参数类型(比如上例不写txt的类型),TypeScript 就会推断参数类型,如果缺乏足够信息,就会推断该参数的类型为 any
。
返回值的类型通常可以不写,因为 TypeScript 自己会推断出来。
function hello(txt: string) {console.log("hello " + txt)
}
上面示例中,由于没有return语句,TypeScript 会推断出函数hello()没有返回值。
不过,有时候出于文档目的,或者为了防止不小心改掉返回值,还是会写返回值的类型。
如果变量被赋值为一个函数,变量的类型有两种写法。
// 写法一
const hello = function (txt: string) {console.log("hello " + txt);
};// 写法二
const hello: (txt: string) => void = function (txt) {console.log("hello " + txt);
};
上面示例中,变量hello被赋值为一个函数,它的类型有两种写法。写法一是通过等号右边的函数类型,推断出变量hello的类型;写法二则是使用箭头函数的形式,为变量hello指定类型,参数的类型写在箭头左侧,返回值的类型写在箭头右侧。
写法二有两个地方需要注意。
首先,函数的参数要放在圆括号里面,不放会报错。
其次,类型里面的参数名(本例是txt)是必须的。有的语言的函数类型可以不写参数名(比如 C 语言),但是 TypeScript 不行。如果写成(string) => void,TypeScript 会理解成函数有一个名叫 string 的参数,并且这个string参数的类型是any。
type MyFunc = (string, number) => number;
// (string: any, number: any) => number
上面示例中,函数类型没写参数名,导致 TypeScript 认为参数类型都是any。
函数类型里面的参数名与实际参数名,可以不一致。
let f: (x: number) => number;f = function (y: number) {return y;
};
上面示例中,函数类型里面的参数名为x,实际的函数定义里面,参数名为y,两者并不相同。
如果函数的类型定义很冗长,或者多个函数使用同一种类型,写法二用起来就很麻烦。因此,往往用type命令为函数类型定义一个别名,便于指定给其他变量。
type MyFunc = (txt: string) => void;const hello: MyFunc = function (txt) {console.log("hello " + txt);
};
上面示例中,type命令为函数类型定义了一个别名MyFunc,后面使用就很方便,变量可以指定为这个类型。
函数的实际参数个数,可以少于类型指定的参数个数,但是不能多于,即 TypeScript 允许省略参数。
let myFunc: (a: number, b: number) => number;myFunc = (a: number) => a; // 正确myFunc = (a: number, b: number, c: number) => a + b + c; // 报错
上面示例中,变量myFunc的类型只能接受两个参数,如果被赋值为只有一个参数的函数,并不报错。但是,被赋值为有三个参数的函数,就会报错。
这是因为 JavaScript 函数在声明时往往有多余的参数,实际使用时可以只传入一部分参数。比如,数组的 forEach()
方法的参数是一个函数,该函数默认有三个参数(item, index, array) => void
,实际上往往只使用第一个参数(item) => void
。因此,TypeScript 允许函数传入的参数不足。
let x = (a: number) => 0;
let y = (b: number, s: string) => 0;y = x; // 正确
x = y; // 报错
上面示例中,函数x只有一个参数,函数y有两个参数,x可以赋值给y,反过来就不行。
如果一个变量要套用另一个函数类型,有一个小技巧,就是使用 typeof
运算符。
function add(x: number, y: number) {return x + y;
}const myAdd: typeof add = function (x, y) {return x + y;
};
上面示例中,函数myAdd()的类型与函数add()是一样的,那么就可以定义成typeof add
。因为函数名add本身不是类型,而是一个值,所以要用 typeof
运算符返回它的类型。
这是一个很有用的技巧,任何需要类型的地方,都可以使用 typeof
运算符从一个值获取类型。
函数类型还可以采用对象的写法。
let add: {(x: number, y: number): number;
};add = function (x, y) {return x + y;
};
上面示例中,变量add的类型就写成了一个对象。
函数类型的对象写法如下。
{(参数列表): 返回值
}
注意,这种写法的函数参数与返回值之间,间隔符是冒号:
,而不是正常写法的箭头=>
,因为这里采用的是对象类型的写法,对象的属性名与属性值之间使用的是冒号。
这种写法平时很少用,但是非常合适用在一个场合:函数本身存在属性。
function f(x: number) {console.log(x);
}f.version = "1.0";
上面示例中,函数f()本身还有一个属性version。这时,f完全就是一个对象,类型就要使用如下对象的写法。
let foo: {(x: number): void;version: string;
} = f;
函数类型也可以使用 Interface
来声明,这种写法就是对象写法的翻版
interface myfn {(a: number, b: number): number;
}var add: myfn = (a, b) => a + b;
上面示例中,interface
命令定义了接口myfn,这个接口的类型就是一个用对象表示的函数。
Function 类型
TypeScript 提供 Function
类型表示函数,任何函数都属于这个类型。
function doSomething(f: Function) {return f(1, 2, 3);
}
上面示例中,参数f的类型就是 Function
,代表这是一个函数。
Function
类型的值都可以直接执行。
Function
类型的函数可以接受任意数量的参数,每个参数的类型都是any,返回值的类型也是any,代表没有任何约束,所以不建议使用这个类型,给出函数详细的类型声明会更好。
箭头函数
箭头函数是普通函数的一种简化写法,它的类型写法与普通函数类似。
const repeat = (str: string, times: number): string => str.repeat(times);
上面示例中,变量repeat被赋值为一个箭头函数,类型声明写在箭头函数的定义里面。其中,参数的类型写在参数名后面,返回值类型写在参数列表的圆括号后面。
注意,类型写在箭头函数的定义里面,与使用箭头函数表示函数类型,写法有所不同。
function greet(fn: (a: string) => void): void {fn("world");
}
上面示例中,函数greet()的参数fn是一个函数,类型就用箭头函数表示。这时,fn的返回值类型要写在箭头右侧,而不是写在参数列表的圆括号后面
下面再看一个例子。
type Person = { name: string };const people = ["alice", "bob", "jan"].map((name): Person => ({ name }));
上面示例中,Person是一个类型别名,代表一个对象,该对象有属性name。变量people是数组的 map()
方法的返回值。
map()
方法的参数是一个箭头函数 (name):Person => ({name})
,该箭头函数的参数name的类型省略了,因为可以从 map()
的类型定义推断出来,箭头函数的返回值类型为 Person。相应地,变量people的类型是Person[]。
至于箭头后面的 ({name})
,表示返回一个对象,该对象有一个属性name,它的属性值为变量name的值。这里的圆括号是必须的,否则 (name):Person => {name}
的大括号表示函数体,即函数体内有一行语句name,同时由于没有return语句,这个函数不会返回任何值。
注意,下面两种写法都是不对的。
// 错误
(name: Person) => ({ name });// 错误
name: (Person) => ({ name });
上面的两种写法在本例中都是错的。第一种写法表示,箭头函数的参数name的类型是Person,同时没写函数返回值的类型,让 TypeScript 自己去推断。第二种写法中,函数参数缺少圆括号。
可选参数
如果函数的某个参数可以省略,则在参数名后面加问号表示。
function f(x?: number) {// ...
}f(); // OK
f(10); // OK
上面示例中,参数x后面有问号,表示该参数可以省略。
参数名带有问号,表示该参数的类型实际上是原始类型|undefined,即它有可能为undefined
。比如,上例的x虽然类型声明为number,但是实际上是number|undefined。
function f(x?: number) {return x;
}f(undefined); // 正确
上面示例中,参数x是可选的,等同于说x可以赋值为undefined。
但是,反过来就不成立,类型显式设为 undefined
的参数,就不能省略。
function f(x: number | undefined) {return x;
}f(); // 报错
上面示例中,参数x的类型是 number|undefined,表示要么传入一个数值,要么传入undefined,如果省略这个参数,就会报错。
函数的可选参数只能在参数列表的尾部,跟在必选参数的后面。
let myFunc: (a?: number, b: number) => number; // 报错
上面示例中,可选参数在必选参数前面,就报错了。
如果前部参数有可能为空,这时只能显式注明该参数类型可能为undefined。
let myFunc: (a: number | undefined, b: number) => number;
上面示例中,参数a有可能为空,就只能显式注明类型包括undefined,传参时也要显式传入undefined。
函数体内部用到可选参数时,需要判断该参数是否为undefined。
let myFunc: (a: number, b?: number) => number;myFunc = function (x, y) {if (y === undefined) {return x;}return x + y;
};
上面示例中,由于函数的第二个参数为可选参数,所以函数体内部需要判断一下,该参数是否为空。
参数默认值
TypeScript 函数的参数默认值写法,与 JavaScript 一致。
function createPoint(x: number = 0, y: number = 0): [number, number] {return [x, y];
}createPoint(); // [0, 0]
上面示例中,参数x和y的默认值都是0,调用createPoint()时,这两个参数都是可以省略的。这里其实可以省略x和y的类型声明,因为可以从默认值推断出来。
function createPoint(x = 0, y = 0) {return [x, y];
}
可选参数与默认值不能同时使用。
// 报错
function f(x?: number = 0) {// ...
}
上面示例中,x是可选参数,还设置了默认值,结果就报错了。
设有默认值的参数,如果传入undefined,也会触发默认值。
function f(x = 456) {return x;
}f2(undefined); // 456
具有默认值的参数如果不位于参数列表的末尾,调用时不能省略,如果要触发默认值,必须显式传入undefined。
function add(x: number = 0, y: number) {return x + y;
}add(1); // 报错
add(undefined, 1); // 正确
参数解构
函数参数如果存在变量解构,类型写法如下。
function f([x, y]: [number, number]) {// ...
}function sum({ a, b, c }: { a: number; b: number; c: number }) {console.log(a + b + c);
}
参数结构可以结合类型别名(type 命令)一起使用,代码会看起来简洁一些。
type ABC = { a: number; b: number; c: number };function sum({ a, b, c }: ABC) {console.log(a + b + c);
}
rest参数
rest
参数表示函数剩余的所有参数,它可以是数组(剩余参数类型相同),也可能是元组(剩余参数类型不同)。
// rest 参数为数组
function joinNumbers(...nums: number[]) {// ...
}// rest 参数为元组
function f(...args: [boolean, number]) {// ...
}
注意,元组需要声明每一个剩余参数的类型。如果元组里面的参数是可选的,则要使用可选参数。
function f(...args: [boolean, string?]) {}
下面是一个 rest 参数的例子。
function multiply(n: number, ...m: number[]) {return m.map((x) => n * x);
}
上面示例中,参数m就是 rest 类型,它的类型是一个数组。
rest 参数甚至可以嵌套。
function f(...args: [boolean, ...string[]]) {// ...
}
rest 参数可以与变量解构结合使用。
function repeat(...[str, times]: [string, number]): string {return str.repeat(times);
}// 等同于
function repeat(str: string, times: number): string {return str.repeat(times);
}
readonly 只读参数
如果函数内部不能修改某个参数,可以在函数定义时,在参数类型前面加上 readonly
关键字,表示这是只读参数。
function arraySum(arr: readonly number[]) {// ...arr[0] = 0; // 报错
}
上面示例中,参数arr的类型是 readonly number[],表示为只读参数。如果函数体内部修改这个数组,就会报错。
void 类型
void
类型表示函数没有返回值。
function f(): void {console.log("hello");
}
上面示例中,函数f没有返回值,类型就要写成 void
。
如果返回其他值,就会报错。
function f(): void {return 123; // 报错
}
上面示例中,函数f()的返回值类型是 void,但是实际返回了一个数值,编译时就报错了。
void 类型允许返回undefined或null。
function f(): void {return undefined; // 正确
}function f(): void {return null; // 正确
}
如果打开了 strictNullChecks
编译选项,那么 void 类型只允许返回undefined。如果返回null,就会报错。这是因为 JavaScript 规定,如果函数没有返回值,就等同于返回undefined。
// 打开编译选项 strictNullChecksfunction f(): void {return undefined; // 正确
}function f(): void {return null; // 报错
}
需要特别注意的是,如果变量、函数参数的类型是 void 类型的函数,那么并不代表不能赋值为有返回值的函数。恰恰相反,该变量、函数参数可以接受返回任意值的函数,这时并不会报错。
变量是 void 类型的函数
type voidFunc = () => void;const f: voidFunc = () => {return 123;
};
上面示例中,变量f的类型是 voidFunc,是一个没有返回值的函数类型。但是实际上,f的值是一个有返回值的函数(返回123),编译时不会报错。
函数参数是 void 类型的函数
function runCallback(callback: () => void) {return callback();
}
使用示例
f(); // 输出 123
runCallback(() => {return 'Callback executed!'
}); // 输出 Callback executed!
这是因为,这时 TypeScript 认为,这里的 void 类型只是表示该函数的返回值没有利用价值,或者说不应该使用该函数的返回值。只要不用到这里的返回值,就不会报错。
这样设计是有现实意义的。举例来说,数组方法Array.prototype.forEach(fn)的参数fn是一个函数,而且这个函数应该没有返回值,即返回值类型是void。
但是,实际应用中,很多时候传入的函数是有返回值,但是它的返回值不重要,或者不产生作用。
const src = [1, 2, 3];
const ret = [];src.forEach((el) => ret.push(el));
上面示例中,push()
有返回值,表示新插入的元素在数组里面的位置。但是,对于forEach()方法来说,这个返回值是没有作用的,根本用不到,所以 TypeScript 不会报错。
如果后面使用了这个函数的返回值,就违反了约定,则会报错。
type voidFunc = () => void;const f: voidFunc = () => {return 123;
};f() * 2; // 报错
上面示例中,最后一行报错了,因为根据类型声明,f()没有返回值,但是却用到了它的返回值,因此报错了。
注意,这种情况仅限于变量、函数参数,函数字面量如果声明了返回值是 void 类型,还是不能有返回值。
function f(): void {return true; // 报错
}const f3 = function (): void {return true; // 报错
};
上面示例中,函数字面量声明了返回void类型,这时只要有返回值(除了undefined和null)就会报错。
除了函数,其他变量声明为void类型没有多大意义,因为这时只能赋值为undefined或者null(假定没有打开 strictNullChecks
) 。
let foo: void = undefined;// 没有打开 strictNullChecks 的情况下
let bar: void = null;
构造函数
JavaScript 语言使用构造函数,生成对象的实例。
构造函数的最大特点,就是必须使用 new
命令调用。
const d = new Date();
上面示例中,date()就是一个构造函数,使用 new
命令调用,返回 Date 对象的实例。
构造函数的类型写法,就是在参数列表前面加上 new
命令。
class Animal {numLegs: number = 4;
}type AnimalConstructor = new () => Animal;function create(c: AnimalConstructor): Animal {return new c();
}const a = create(Animal);
下面详细解释每一部分:
class Animal {numLegs: number = 4;
}
- 定义了一个名为
Animal
的类。 - 该类有一个成员变量
numLegs
,类型为number
,默认值为 4。 - 这表示每个
Animal
实例numLegs
都为4。
type AnimalConstructor = new () => Animal;
- 定义了一个类型别名
AnimalConstructor
。 new () => Animal
表示:这是一个构造函数类型,接受零个参数,返回一个Animal
实例。- 这样可以用来约束哪些构造函数可以作为参数进行传递。
function create(c: AnimalConstructor): Animal {return new c();
}
- 定义了一个名为
create
的函数。 - 参数
c
的类型是AnimalConstructor
,即必须是一个可以用new
关键字实例化并返回Animal
的构造函数。 - 函数体中通过
new c()
创建并返回一个新的Animal
实例。
const a = create(Animal);
- 调用
create
函数,传入Animal
类本身(类在 TypeScript/JavaScript 中本质上就是构造函数)。 - 返回值赋给变量
a
,此时a
是一个Animal
实例。
如果 Animal
构造函数需要参数,比如 constructor(numLegs: number) { ... }
,则 AnimalConstructor
类型也要相应调整为 new (numLegs: number) => Animal
。
构造函数还有另一种类型写法,就是采用对象形式。
type F = {new (s: string): object;
};
上面示例中,类型 F 就是一个构造函数。类型写成一个可执行对象的形式,并且在参数列表前面要加上 new
命令。
某些函数既是构造函数,又可以当作普通函数使用,比如 Date()。这时,类型声明可以写成下面这样。
type F = {new (s: string): object;(n?: number): number;
};
上面示例中,F 既可以当作普通函数执行,也可以当作构造函数使用。