typescript(3)- 函数详解 | 青训营笔记

128 阅读7分钟

这是我参与「第四届青训营 」笔记创作活动的第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 中,如果想 显式 地指定返回值为 undefinednull,必须 return 对应类型

function fn(): undefined {
  return undefined;
}

function Fn(): null {
  return null;
}

function fn(): undefined {} // error

function Fn(): null {} // error

所以 voidts 中与 undefined 重大的区别之一void 可以指定没有 return 的函数,而且 void 类型的数据只能被赋值 nullundefined 可以在下面这个例子中被验证

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;
}

只声明函数重载,而不实现函数是不允许的

但一般 函数重载 不会单纯地只用于声明一个函数
函数重载:重载的是 相同参数列表参数类型不同重名函数,区别于设置函数参数 联合类型

例如,我们只想实现相同类型的参数相加(stringnumber),而不允许不同类型参数相加

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); 

那能不能使用 undefinednull 禁用 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 的类型,大家不用死记硬背,用类型推导就可以获取到变量的类型

image.png

「参考文章」

TypeScript 函数中的 this 参数 - 腾讯云开发者社区-腾讯云 (tencent.com)