这是我参与「第四届青训营 」笔记创作活动的第10天
「前言」
在上一篇文章中,我们使用 接口 约束 函数 的类型,为了一个函数声明一个接口显然在某些时候不是一个划算的决定
「函数类型扩展」
在 js 中函数有多种声明方式,例如 function 函数() {}、变量 = function(){}、 箭头函数,那么对应着 ts 中约束函数的多种方式
直接声明
function add(x: number, y: number): number {
return x + y;
}
就如上面这个例子,在 参数列表 和 返回值 处约束类型
匿名函数
const add: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y;
}
const add = function (x: number, y: number): number {
return x + y;
}
const add: (x: number, y: number) => number = function (x, y) {
return x + y;
}
所有变量在设置初始值的时候就有 类型推断,即使是函数
这三种方式都是等效的,利用 类型推断 可以少写点代码
箭头函数
同理,箭头函数也有三种声明方式
const add: (x: number, y: number) => number = (x: number, y: number): number => {
return x + y
}
const add = (x: number, y: number): number => {
return x + y
}
const add: (x: number, y: number) => number = (x, y) => {
return x + y
}
还可以使用 类型一体化 方式声明
const add: { (x: number, y: number): number } = (x, y) => {
return x + y
}
通过 {} 让类型声明写在一起,方便阅读
利用 type 关键词
type 关键字在后续中的文章会详细讲解,这里只需要记住它的写法
type Add = (x: number, y: number) => number;
const add: Add = (x, y) => x + y
注意
1. 省略返回值类型
对于没有返回值的函数等效为指定返回值为 void
const add: (x: number, y: number) => void = (x, y): number => x + y;
const add: (x: number, y: number) => void = (x, y) => x + y;
在这个案例中即使在声明函数类型中指定返回值为 void,但是还是可以被实际返回值给覆盖掉,就如下面这个例子一样
function fn(x: number) {
return x;
}
虽然没有设置返回值类型(等同于 void),但是实际返回值类型为 number
在某些时候忘写了返回值类型,但是实际返回值类型不为 void,任何类型可以在这个时候可以覆盖掉 void
函数如果没有返回值等效于返回值为 void,在 js 中不设置返回值和 return undefined 等效,而在 ts 中,如果想 显式 地指定返回值为 undefined 和 null,必须 return 对应类型
function fn(): undefined {
return undefined;
}
function Fn(): null {
return null;
}
function fn(): undefined {} // error
function Fn(): null {} // error
所以 void 在 ts 中与 undefined 重大的区别之一,void 可以指定没有 return 的函数,而且 void 类型的数据只能被赋值 null 和 undefined 可以在下面这个例子中被验证
function fn(): void {
return null;
}
function Fn(): void {
return undefined;
}
function fun():void { }
「参数列表」
在 js 中的调用函数的时候传入参数可以与 arguments 建立一一对应,也可以传入与参数列表不同的个数的参数,这时候多于的参数或者缺少的参数失去了与 arguments 映射的关系,而在 ts 中,这种情况是不被允许的
function fn(x: number) { }
fn(1)
fn(); // error
fn(1, 2); // error
可选参数
function fn(x?: number) { }
fn(1)
fn();
fn(1, 2); // error
在参数的 类型注解 前加上 ? 可以设置参数为 可选参数
注意:可选参数 必须位于 必须参数 之后
默认参数
function fn(x = 1) { }
如果默认参数 省略类型注解,会被自动 类型推断,等效于
function fn(x: number = 1) { }
剩余参数
在 js 中可以设置 剩余参数, 获取多于参数列表剩余的参数,在 ts 中也可以指定剩余参数的类型
function fn(x: number, ...arg: number[]) { }
fn(1, 2, 3, 4);
fn(1, 2, 3, '4'); // error
「函数重载」
在 ts 除了之前说的方式声明函数也可以通过 函数重载 的方式声明函数
function fn(x: number): number; // 声明函数重载
function fn (x) { // 函数实现
return x;
}
只声明函数重载,而不实现函数是不允许的
但一般 函数重载 不会单纯地只用于声明一个函数
函数重载:重载的是 相同参数列表 而 参数类型不同 的 重名函数,区别于设置函数参数 联合类型
例如,我们只想实现相同类型的参数相加(string 和 number),而不允许不同类型参数相加
function add(x: string, y: string): string
function add(x: number, y: number): number
function add(x: string | number, y: string | number): string | number {
if (typeof x === 'string' && typeof y === 'string') {
return x + y
} else if (typeof x === 'number' && typeof y === 'number') {
return x + y
}
}
console.log(add(1, 2))
console.log(add('a', 'b'))
console.log(add(1, 'a')) // error
这个例子中也隐藏了 类型守卫,在后续的文章中会详细展开讲解
注意
function fn(x: number): string;
function fn(x) {
return x;
}
这里的 返回值 与 函数重载 声明不符,说明在很多时候我们是可以骗过编译器,但失去了类型校验
所以除了在 函数重载 处声明类型,也要在 函数实现 的时候声明类型,确保可以在函数内部加强对类型的判断
「伪参数 this」
因为 this 是全局和函数内部自定义的,而我们想要指定函数中 this 的类型,只能在参数列表中指定类型(而且 this 只放在函数列表首位),而调用的函数时候,自动忽略掉 this的位置
function fn(this, a) {
console.log(this);
console.log(this.name);
console.log(a);
}
fn(1);
this 默认类型为 any
我们没指定 this 类型,或者手动添加 this(但不指定类型),相当于 this: any
如果在 tsconfig.json 中设置了 "noImplicitThis": true,会因为想使用 this,而不声明 this 报错
function fn(a) {
console.log(this); // error "this" 隐式具有类型 "any",因为它没有类型注释。
console.log(this.name); // error
console.log(a);
}
fn(1);
指定 this 类型
function fn(this: { name: string }, a: number) {
console.log(this);
console.log(this.name);
console.log(a);
}
fn({ name: '张三' }); // error: 类型为“void”的 "this" 上下文不能分配给类型为“{ name: string; }”的方法的 "this"。
fn.call({ name: '张三' }, 2) // success,这里也骗过了编译器
在很多时候函数和方法指定 this类型,都会被替换为 void,即使你将 this 类型指定为 Window,也会被替换为 void,虽然指定了 this 类型可以在函数中正常使用,但在调用中依然报错
也就是说,不可以给 this 设置一个根本不可能正确的类型
class Rectangle {
width: number;
height: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
getAreaFunction() {
return function () {
return this.width * this.height; // error "this" 隐式具有类型 "any",因为它没有类型注释
};
}
}
在这个例子中 this 指向对于开发者来说混乱,我们这时候就应该约束 this 的类型,减少代码的 “歧义”
class Rectangle {
width: number;
height: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
getAreaFunction() {
return function (this: Rectangle) { // error: 即使指定了 this 也依然报错
return this.width * this.height;
};
}
}
这里的 this 依然是 void 类型
具体的解决方法会在有关 类 的类型文章中做出解答
禁用 this
很多时候我们不希望在函数中使用 this,可以使用这种方式
function fn(this: void, a) {
console.log(this); // this 本身不报错,但在执行阶段会报错
console.log(this.name); // error: 类型“void”上不存在属性“name”
console.log(a);
}
fn(1);
那能不能使用
undefined和null禁用this
答案:不行
function fn(this: undefined, a) {
console.log(this);
console.log(this.name); // this 对象可能为“未定义”。
console.log(a);
}
fn(1); // error: 类型为“void”的 "this" 上下文不能分配给类型为“undefined”的方法的 "this"。
依然不能修改掉 this 的类型
回调函数中的 this
const button = document.querySelector("button");
button.addEventListener("click", function () {
console.log(this); // success,自动推导为 HTMLButtonElement 类型
});
如果回调函数使用在外部定义的函数没指定 this 类型,外部函数中的 this 也会被当做 any,就像前文说的一样
const button = document.querySelector("button");
button.addEventListener("click", handleFn);
function handleFn(this: HTMLButtonElement) {
console.log(this);
}
关于 DOM 的类型,大家不用死记硬背,用类型推导就可以获取到变量的类型