1

前言

个人学习TS的总结, 主要参考官方文档,对于其中不太容易理解的点进行剖析;写下来,主要是自己为了巩固知识点,并给大家分享下,如有不对之处,请指出~

类型系统

基本类型

  • 数字(number):包括浮点数,以及 NaN、±Infinity。
  • 字符串(string):字符串型。
  • 布尔(boolean):布尔型,即 { true, false }。
  • null:即 { null }。
  • undefined:即 { undefined }。 默认情况下null和undefined是所有类型的子类型,可以赋值给任何类型;当你指定了--strictNullChecks标记,null和undefined只能赋值给void和它们各自
  • symbol:符号类型。
  • never:表示永远无法到达的终点,是任何类型的子类型,也可以赋值给任何类型,但除自身之外任何类型都不能赋值给它。一个中途抛出错误,或者存在死循环的函数永远不会有返回值,其返回类型是 never
  • void:表示没有任何类型,没有返回值;它等于 { null, undefined }。
  • 元组(tuple):特殊的数组,指定数组每项具体的类型,一一映射。eg: let tuple1: [number, string] = [12, 'aa']
  • 枚举(enum):一组固定值的特定映射。eg: enum sex { MALE, FEMALE} 等同于 enum sex {MALE=0, FEMALE=1} sex.MALE // 取值
  • 任意类型(any)
  • object:表示非原始类型,也就是除number,string,boolean,symbol,null或undefined之外的类型。用来描述一种非基础类型,所以一般用在类型校验上,比如作为参数类型。如果参数类型是 object,那么允许任何对象数据传入

另外,所有原始类型的字面量本身也可作为类型使用,其外延只包括自身

高级类型

联合类型

表示一个值可以是几种类型之一。 竖线(|)分隔;若一个值是联合类型,只能访问此联合类型的所有类型里共有的成员

交叉类型

将多个类型合并为一个类型。 竖线(&)分隔;把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性,多用于mixins

映射类型

从旧类型创建新类型的一种方式

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
}
type Partial<T> = {
    [P in keyof T]?: T[P];
}

interface Person {
    name: string;
    age: number;
}

// 调用
type PersonPartial = Partial<Person>;
type ReadonlyPerson = Readonly<Person>;
// 结果
interface PersonPartial {
    name?: string;
    age?: number;
}
interface PersonReadonly {
    readonly name: string;
    readonly age: number;
}

类型保护

1、用户自定义类型保护:定义函数,返回类型谓词(语法:parameterName is Type)
2、typeof类型保护:只有两种形式能被识别: typeof v === "typename"和 typeof v !== "typename", "typename"必须是 "number", "string", "boolean"或 "symbol" 原始类型,若为除前四个的其他字符串,将不被识别为类型保护
3、instanceof 类型保护:过构造函数来细化类型的一种方式。语法: 实例 instanceof 构造函数

function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][] {
 return names.map(n => o[n]);
}

interface Person {
   name: string;
   age: number;
}
let person: Person = {
   name: 'Jarid',
   age: 35
};
let strings: string[] = pluck(person, ['name']); // ok, ['Jarid]

其中
keyof T: 索引类型查询操作符 (此实例中,为person的属性name、age,值等于'name'|'age')
K extends keyof T:泛型约束
T[K]: 索引访问操作符(此实例中,为person['name'])

interface 与 type 声明类型的区别

定义类型有两种方式: 接口(interface) 和类型别名(type alias), 它们的主要区别如下:

1、interface 只能定义对象类型或者函数, type 还可以定义组合类型,交叉类型(&,类似并集),联合类型(|,类似交集),原始类型
2、interface 方式可以实现接口的 extends 和 implements , 而 type alias 则不行。
3、interface 可以实现接口的合并,但 type alias 则不行。

兼容性

兼容可以简单理解可否赋值,A = B (A 兼容/包含 B)

子类型兼容性

1、Any最“宽”。兼容其它所有类型(换言之,其它类型都是Any的子类型)
2、Never最“窄”。不兼容任何其它类型
3、Void兼容Undefined和Null
4、其它类型都兼容Never和Void

例如:

type point1D = { x: number } // 父 A
type point2D = { x: number, y: number } // 子 B
A = B // ok

当且仅当类型 B 是 A 的子集时,A 兼容 B,B 可以被当成 A 处理
A = B

函数兼容性判定

 参数:要求对应参数的类型兼容,数量允许多余 (多的包含少的,即参数允许多,不许少)
 返回值:类型兼容 (范围更广, 赋值变量是被赋值的变量的子类型)

类型断言

可以用来手动指定一个值的类型
语法:<类型>值值 as 类型 (在 tsx 语法(React 的 jsx 语法的 ts 版)中必须用后一种。)

使用内置对象作为类型

ECMAScript 内置对象: Error、RegExp、Date、Boolean 等
DOM/BOM 内置对象: Document、HTMLElement、Event、NodeList、MouseEvent 等

参考:
http://kbscript.com/2017/01/2...
https://juejin.im/post/5c2723...

泛型

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
类型变量(类型参数) T

泛性约束 extends

interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
}

泛型接口

使用含有泛型的接口来定义函数形状

interface ConfigFn{
    <T>(value:T):T;
}

var getData:ConfigFn=function<T>(value:T):T{
    return value;
}

getData<string>('张三');
getData<string>(1243);  //错误
// 把类型参数提到接口名上
// 写法一:
interface ConfigFn<T>{
    (value:T):T;
}

var getData:ConfigFn<string>=function<T>(value:T):T{
    return value;
}

getData('20');  /*正确*/


// 写法二:
interface ConfigFn<T>{
    (value:T):T;
}

function getData<T>(value:T):T{
    return value;
}

var myGetData:ConfigFn<string>=getData;     
myGetData('20');  /*正确*/
myGetData(20)  //错误

泛型类

使用泛型来定义类

  class GenericNumber<T> {
      zeroValue: T;
      add: (x: T, y: T) => T;
  }

  let myGenericNumber = new GenericNumber<number>();
  myGenericNumber.zeroValue = 0;
  myGenericNumber.add = function(x, y) { return x + y; };

抽象类

抽象类: abstract 修饰,里面可以没有抽象方法。但有抽象方法(abstract method)的类必须声明为抽象类(abstract class)

1、抽象类是不允许被实例化的
2、抽象类中的抽象方法必须被子类实现,不能自己实现
3、可以使用修饰符

注意:如果子类继承的是一个抽象类,子类必须实现父类里的抽象方法,不然的话不能实例化,会报错。

类与接口的关系

类继承类(class extends class)
类实现接口(class implements interface)
接口继承接口(interface extends interface)
接口继承类(interface extends class)

混合类型

    interface Counter {
        (start: number): string;
        interval: number;
        reset(): void;
    }

    function getCounter(): Counter {
        let counter = <Counter>function (start: number) { }; // 这里有点疑问?
        counter.interval = 123;
        counter.reset = function () { };
        return counter;
    }

    let c = getCounter();
    c(10);
    c.reset();
    c.interval = 5.0;

参考: https://ts.xcatliu.com/advanc...

接口

接口的作用:在面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范;在程序设计里面,接口起到一种限制和规范的作用。接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它只规定这批类里必须提供某些方法,提供这些方法的类就可以满足实际需要。 typescrip中的接口类似于java,同时还增加了更灵活的接口类型,包括属性、函数、可索引和类等。

面向对象语言:接口是对行为和动作的抽象,而具体如何行动需要由类(classes)去实现(implement)
ts: 对类的部分行为进行抽象,对对象的形状进行描述; 把不同类中的共性抽取作为接口,再由类分别去实现(implement)

简而言之,定义规范标准,用于复用(与普通类型约束相比)。

interface Person {
    readonly id: number; // 只读属性
    name: string; // 确定属性
    age?: number; // 可选属性
    [propName: string]: string; // 任意属性
    [index: number]: string; // 可索引属性
    (name: string, age: number): void; // 函数
    currentTime: Date; // 类类型的属性 (类实现接口)
    setTime(d: Date); // 类类型的方法
}

注意:
1、使用 [propName: string] 定义了任意属性取 string 类型的值。
需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
2、 只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候。给对象变量赋值后,只读属性不能再改变。
3、当一个类实现了一个接口时,只对其实例部分进行类型检查。 constructor存在于类的静态部分,所以不在检查的范围内

接口继承接口

  interface Shape5 {
      color: string;
  }

  interface Square5 extends Shape5 {
      sideLength: number;
  }

  let square = <Square5>{}; // 定义对象,使用断言
  console.log(square) // {}
  // let square:Square5 = {color: '', sideLength: 0}
  square.color = "blue";
  square.sideLength = 10;
  console.log(square)

另:一个接口可以继承多个接口

接口继承类
接口继承类时,会继承类的所有成员但不包含实现

  class Person {
      type: string // ❗️这里是类的描述
  }

  interface Child extends Person { // ❗️Child 接口继承自 Person 类,因此规范了 type 属性
      log(): void
      // 这里其实有一个 type: string
  }

  // ⚠️ 上面的 Child 接口继承了 Person 对 type 的描述,还定义了 Child 接口本身 log 的描述

  // 🥇 第一种写法
  class Girl implements Child {
      type: 'child' // 接口继承自 Person 的
      log() {} // 接口本身规范的
  }

  // 🥈 第二种写法
  class Boy extends Person implements Child { // 首先 extends 了 Person 类,然后还需满足 Child 接口的描述
      type: 'child'
      log() {}
  }

当接口继承了一个拥有私有(private)或受保护(protected)的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)。

class Person {
  private type: string // ❗️这里是类的描述
}

interface Child extends Person { // ❗️Child 接口继承自 Person 类,因此规范了 type 属性
  log(): void
  // 这里其实有一个 type: string
}

// ⚠️ 上面的 Child 接口继承了 Person 对 type 的描述,还定义了 Child 接口本身 log 的描述


// 写法 (only)
class Boy extends Person implements Child { // 首先 extends 了 Person 类,然后还需满足 Child 接口的描述
  type: 'child'
  log() {}
}

函数

定义函数方式:

  let mySum = function (x, y) {
    return x + y;
  };

1、函数声明

function sum(x: number, y: number): number {
    return x + y;
}

2、函数表达式

  let mySum = function (x: number, y: number): number {
      return x + y;
  };

  或者

  let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
      return x + y;
  };

可选参数:(?:) 必须接在必需参数后面
参数默认值: 相当于可选参数,任意位置
剩余参数:(...rest)必须是在最后,与 es6 相同

重载

java中方法的重载:重载指的是两个或者两个以上同名函数,但它们的参数不一样,这时会出现函数重载的情况。
typescript中的重载:通过为同一个函数提供多个函数类型定义,一个函数体实现多种功能的目的。
ts为了兼容es5 以及 es6 重载的写法和java中有区别。

重载:同名函数,参数不一样。允许一个函数接受不同数量或类型的参数时,作出不同的处理

  function reverse(x: number): number;  // 函数定义
  function reverse(x: string): string;  // 函数定义
  function reverse(x: number | string): number | string {  // 函数实现
      if (typeof x === 'number') {
          return Number(x.toString().split('').reverse().join(''));
      } else if (typeof x === 'string') {
          return x.split('').reverse().join('');
      }
  }

多态

父类定义一个方法不去实现,让继承它的子类去实现, 每一个子类有不同的表现(用于继承)
注意:使用多态基础是类的继承或者接口实现。

抽象类

typescript中的抽象类:
1、它是提供其他类继承的基类,不能直接被实例化。
2、用abstract关键字定义抽象类和抽象方法,抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。
3、abstract抽象方法只能放在抽象类里面
用途:抽象类和抽象方法用来定义标准 。 (抽象类:Animal 这个类要求它的子类必须包含eat方法,否则报错;多态:子类继承父类,子类可以不实现该方法,但使用多态就是子类为了实现方法)

模块系统

从ECMAScript 2015开始,JavaScript引入了模块的概念。TypeScript沿用此概念,在TS 中,模块分为内部模块和外部模块,自typescript 1.5 后,内部模块称作“命名空间”,外部模块称作“模块”

命名空间(namespace)(又名内部模块)

同Java的包、.Net的命名空间一样,TypeScript的命名空间将代码包裹起来,通过export关键字对外暴露需要在外部访问的对象。
在代码量较大的情况下,为了避免各种变量命名相冲突,可将相似功能的函数、类、接口等放置到命名空间内

主要用于组织代码,解决命名冲突,会在全局生成一个对象,定义在namespace内部的类都要通过这个对象的属性访问

模块(module)(又名外部模块)

主要是解决加载依赖关系的,侧重代码的复用。跟文件绑定在一起,一个文件就是一个module,模块写法和ES6一样

三斜线指令

1、书写一个全局变量的声明文件(书写一个全局变量的声明文件时,如果需要引用另一个库的类型)
2、依赖一个全局变量的声明文件

命名空间和模块的区别:

  • 命名空间:内部模块,主要用于组织代码,避免命名冲突。
  • 模 块:ts的外部模块的简称,侧重代码的复用,一个模块里可能会有多个命名空间。

adaxxl
72 声望0 粉丝