TypeScript 从头学(二):函数、对象、interface

4 阅读3分钟

函数

函数声明时,需要给出参数的类型和返回值的类型。参数类型不指定时,会推断为 any。返回值类型 TypeScript 会自己推断,但是推荐写明确。

  • 实际使用时,一般用 type 为函数类型定义一个别名。
  • 函数的实际参数个数可以少于类型指定的参数个数,但不能多。
  • x?: number 代表可选参数,需要放在末尾。
  • x: numer | undefined = 0 类型的参数如果在前面,需要传 undefined 才能使用默认值
// 声明函数的写法
const hello = function (txt: string) {
  console.log('hello ' + txt);
}

const hello: (txt: string) => void = function (txt) {
  console.log('hello ' + txt);
};

// 这种方法适用于函数本身存在属性时
let hello: {
  (txt: string): void;
};

函数重载

有些函数可以接受不同类型或不同个数的参数,并且根据参数的不同,会有不同的函数行为,这种行为称为函数重载(function overload)。

  • 所有类型的参数必须在一个函数内处理。
  • 不同重载的参数不能冲突。
  • TypeScript 从上向下检查类型,所以范围越宽的,需要放在后面。
function reverse(str:string):string;
function reverse(arr:any[]):any[];
function reverse(stringOrArray: string|any[]): string|any[] {
  if (typeof stringOrArray === 'string') {
    return stringOrArray.split('').reverse().join('');
  } else {
    return stringOrArray.slice().reverse();
  }
}

构造函数

  • 构造函数的类型的写法,是在参数列表前加 new 关键字。
  • JavaScript 中,class 本质上是一种构造函数。
class Animal {
  numLegs: number = 4;
}

type AnimalConstructor = new () => Animal;

function create(c: AnimalConstructor): Animal {
  return new c();
}

const a = create(Animal);

// 对象形式定义
type F = {
  new (s:string): object;
};

// 既可以是构造函数,又可以是普通函数
type F = {
  new (s:string): object;
  (n?:number): number;
}

对象

  • 可选属性 x?: number。
  • 只读属性 readonly。只读属性只能在初始化时赋值,之后不能修改。如果只读属性是个对象,对象的属性可以修改,但是不能整个替换对象。
// 写法一
type MyObj = {
  x: number;
  y: number;
};

const obj: MyObj = { x: 1, y: 1 };

// 写法二
interface MyObj {
  x: number;
  y: number;
}

const obj: MyObj = { x: 1, y: 1 };

interface

interface 是对象的模板,可以看做是一种类型的约定。使用了某个模板的对象,就拥有了指定的类型结构。

// 声明 interface
interface Person {
  firstName: string;
  lastName: string;
  age: number;
}

// 使用 interface
const p: Person = {
  firstName: 'John',
  lastName: 'Smith',
  age: 25,
};

继承

  • 继承 interface:interface 可以多重继承,但要求继承或覆盖时,属性兼容。
  • interface 可以继承 type
  • interface 可以继承 class
interface Style {
  color: string;
}

interface Shape {
  name: string;
}

interface Circle extends Style, Shape {
  radius: number;
}

接口合并

TypeScript 会对同名的 interface 进行自动合并:

  • 同名属性有多个类型声明时,要求类型兼容
  • 同名方法合并时,会发生函数重载,一般情况下越后面的优先级更高。但是,如果函数的参数是字面量,会排在函数重载的最前面。
interface Document {
  createElement(tagName: any): Element;
}
interface Document {
  createElement(tagName: "div"): HTMLDivElement;
  createElement(tagName: "span"): HTMLSpanElement;
}
interface Document {
  createElement(tagName: string): HTMLElement;
  createElement(tagName: "canvas"): HTMLCanvasElement;
}

// 等同于
interface Document {
  createElement(tagName: "canvas"): HTMLCanvasElement;
  createElement(tagName: "div"): HTMLDivElement;
  createElement(tagName: "span"): HTMLSpanElement;
  createElement(tagName: string): HTMLElement;
  createElement(tagName: any): Element;
}

interface 与 type 的异同

  • type 可以表示非对象类型,interface 只能表示对象类型(包括数组、函数等)。
  • interface 可以继承,type 不支持继承。type 通过 & 符号,实现在其他 type 包括 interface 的基础上添加属性,类似于继承;interface 使用的是继承。
// interface 继承 type
type Foo = { x: number; };

interface Bar extends Foo {
  y: number;
}

// type
interface Foo {
  x: number;
}

type Bar = Foo & { y: number; };
  • 同名 interface 会自动合并,同名 type 会报错。
  • interface 不支持属性映射。
  • this 关键字只能用于 interface。
  • type 可以扩展原始数据类型,interface 不行。
  • interface 无法表达某些复杂类型(比如交叉类型和联合类型),type 可以。
type A = { /* ... */ };
type B = { /* ... */ };

// 联合类型
type AorB = A | B;

// 为联合类型添加属性
type AorBwithName = AorB & {
  name: string
};

综上所述,如果有复杂的类型运算,只能选择 type。在一般情况下,interface 的灵活性更高,建议优先使用。