# TypeScript基础一

# TypeScript特点

相较于JavaScript,TypeScript最大特点是类型约束。

# TypeScript环境准备

# 全局安装 TypeScript 环境
npm i -g typescript
# 验证 TypeScript 是否安装成功
tsc -v
1
2
3
4

# tsc命令常用选项

  • 初始化TypeScript项目,并创建tsconfig.json文件

    tsc --init

  • 监听ts变化,实时编译

    tsc -w

  • 指定tsconfig文件路径,编译ts

    tsc -p ./path/to/tsconfig.json

  • 构建项目

    tsc -b

  • 输出tsconfig.json所有编译配置项说明

    tsc --all

  • 指定要生成的模块代码

    如生成commonjs模块代码,

    tsc -m commonjs XXX.ts

  • 更多信息,可以通过它学习tsc命令

    tsc -h

# 数据类型

# 1、数据类型声明

// 完整变量声明
var 变量名:变量类型 =;

// 完整函数声明
function 函数名(参数名:参数类型,...):返回值类型 {}

// 表达式中函数声明
var 函数名:(参数名:参数类型,...)=>返回值类型 = function(参数名:参数类型,...):返回值类型 {}

// 接口中的函数声明
interface IFunc {
  函数名(参数名:参数类型,...):返回值类型;
}

// 类中的函数声明
class CFunc {
  函数名(参数名:参数类型,...):返回值类型 {}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 2、null与undefined

两者均既可以当类型使用,又可以当值使用。

# 3、void

// undefined可以赋值给void类型(详细可以看`类型兼容·基本数据类型兼容`内容介绍)
const v: void = undefined;

// 当函数通过类型别名()=>void定义返回值类型时,允许返回其他类型值
type VoidFn = () => void;
const func: VoidFn = () => true;
console.log(func());// 返回true

// 当函数直接定义返回值类型为void时,而实际返回值非void,编译时报错:Type 'boolean' is not assignable to type 'void'.
const func2 = function():void{
  return true;
}
console.log(func2());// 返回true
1
2
3
4
5
6
7
8
9
10
11
12
13

# 4、never

任何类型无法赋值给never类型。该关键字也无法作为值使用。当函数抛异常或不返回值(如死循环了)总是返回never。

const a: boolean = never;// 报错:'never' only refers to a type, but is being used as a value here.

const b: never = true;// 报错:Type 'boolean' is not assignable to type 'never'.

// 先后报错:
// Type 'any' is not assignable to type 'never'. 
// 'never' only refers to a type, but is being used as a value here.
const c: never = never;
1
2
3
4
5
6
7
8

# 5、any与unknown

在类型不确定时使用。二者区别是,编译阶段进行属性访问或函数调用时,unknown会做类型检查,而any不会。建议用unknown替换any

let tAny:any = 2;
tAny = [1];
// 不做类型检查
tAny.toFixed(2);

let tUnknown:unknown = 3;
tUnknown = [1];
// 会做类型检查
tUnknown.toFixed(2);
1
2
3
4
5
6
7
8
9

# 6、number

数字类型

let num: number = 10;
num = 0xfff + 0x1;
// 虽然写法是16进制,但是最终结果还是以10进制显示
console.log(num); //4096
1
2
3
4

# 7、string

字符串类型

const str: string = `Hello TypeScript`;
1

# 8、boolean

布尔类型

const bool: boolean = false;
1

# 9、object

  • 对象类型

    注意类型首字母大小写,object不包含基础数据类型,而Object包含

const obj: {a: string, b: number} = {
  a: 'hello',
  b: 10
};
const lowerCaseObj: object = 1; // 报错: Type 'number' is not assignable to type 'object'.
const upperCaseObj: Object = 1;
1
2
3
4
5
6
  • 数组类型

    类型声明语法为Array<类型>,其简写为类型[]

const arr1:Array<number> = [1,2,3];
const arr2:string[] =  ['a','b','c'];
const arr3: (string|number|boolean)[] = [1,true, 'abc'];
1
2
3
  • 元组类型

    元组是指元素数量明确,且每个元素类型也明确的一个数组。赋值时须与类型声明顺序一一对应

let tuple: [string, number, object] = ["undefined", 10, {}];
console.log(tuple); // [ 'undefined', 10, {} ]
console.log(tuple[3]) // 报错: Tuple type '[string, number, object]' of length '3' has no element at index '3'.
1
2
3

# 10、symbol

es2015新增的数据类型,该类型的特点是值不可改变且唯一

const s1: symbol = Symbol('hello');
const s2: symbol = Symbol('hello');
console.log(s1 === s2); // false
1
2
3

# 11、bigint

es2020新增的数据类型,解决大数精度问题

// 使用新特性,建议tsconfig配置项target设置为esnext
const b1: bigint = 10n;
const b2: number = 10;
console.log(b1 === b2); // false
1
2
3
4

# 12、类型断言

  • 指定编译时的类型,当类型不兼容时会报错,但不影响运行结果。

  • 提供了两种指定方式,关键字as和泛型。记住一句话,若类型x与y具有相同属性,则二者互能为彼此断言。

const str:string = "The best of programming language";

console.log(str as number); // 报错:Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.

console.log(str as any as number); // 双重断言,这是个漏洞,实际切勿使用。借助了任何类型可以被断言为any,any被可以被断言为任何类型。

console.log(<string>str);
1
2
3
4
5
6
7
  • 类型转换与类型断言

类型断言只影响Typescript编译时的类型,类型断言语句在编译结果中会被删除。所以类型断言并不是类型转换,不会真的影响变量类型

console.log(1 as boolean); // 1
console.log(Boolean(1)); // true
1
2
  • 类型声明与类型断言

x断言为y,x和y有重叠部分即可;x声明为y,x必须具备y所有属性和方法。类型声明比类型断言更严格,优先使用类型声明。

interface Animal{
  name: string;
}
interface Dog{
  name: string;
  run(): void;
}
const animal: Animal = {
  name: '旺财'
}
let dog:Dog = animal; // 报错:Property 'run' is missing in type 'Animal' but required in type 'Dog'.
1
2
3
4
5
6
7
8
9
10
11

# 13、类型推断

当变量没有显式类声明类型时,可以从以下几处推断类型:变量初始化,函数参数默认值,函数返回值

# 交叉类型

直接举例认识,

interface A {
  name: string;
}
interface B {
  age: number;
}
// 同时具备A和B属性
type C = A & B;
const p:C = { name: "David", age: 42 };
console.log(`name: ${p.name},age: ${p.age}`);
1
2
3
4
5
6
7
8
9
10

同属性重复声明时,不起冲突说明都满足,起冲突则返回never类型,在赋值时报错。

type AgeType = {age: number} & {age: 10};
const i:AgeType  = {age: 10};

type AgeType2 = {age: 5} & {age: 10};
const j: AgeType2 = {age : 10}; // 报错,Type 'number' is not assignable to type 'never'.
1
2
3
4
5

注:若是使用继承方式,属性重复声明冲突了,在继承声明时报错。

# 联合类型

联合类型不能直接调用非共有方法。

interface Bird {
  fly: ()=> void;
  layEggs: ()=> void;
}

interface Fish {
  swim: ()=> void;
  layEggs: ()=> void;
}

// 返回类型可能是Fish或Bird
const getSmallPet = function(): Fish | Bird {
  const fish: Fish = {
    swim: () => {
      console.log("I can swim");
    },
    layEggs: () => {
      console.log("I can lay eggs");
    }
  };
  const bird: Bird = {
    fly: () => {
      console.log("I can fly");
    },
    layEggs: () => {
      console.log("I can lay eggs");
    }
  };
  return Math.floor(Math.random() * 2) === 0 ? fish : bird;
};
const pet = getSmallPet();
pet.layEggs();
pet.fly(); // 报错:Property 'fly' does not exist on type 'Bird | Fish'. Property 'fly' does not exist on type 'Fish'.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 1、类型保护措施(类型区分)

  • 类型断言
if ((<Fish>pet).swim) {
  (<Fish>pet).swim();
} else {
  (<Bird>pet).fly();
}
1
2
3
4
5
  • 类型谓词(断言函数)
function isBird(pet: Fish | Bird): pet is Bird {
  return (<Bird>pet).fly !== undefined;
}
if (isBird(pet)) {
  pet.fly();
} else {
  pet.swim();
}
1
2
3
4
5
6
7
8
  • typeof

类型判断,如typeof A

  • instanceof

某实例是否是某类型的实例,如b instanceOf B

# 2、可辨识联合

也被称为标签联合或代数数据类型。包含三要素:

1.可辨识:具有值唯一的属性

2.联合:一个类型别名包含了这些类型的联合

3.类型保护:有对该属性作类型保护

interface Square {
  kind: "square"; // 可辨识
  size: number;
}
interface Rectangle {
  kind: "rectangle";
  width: number;
  height: number;
}
interface Circle {
  kind: "circle";
  radius: number;
}
type ShapeType = Square | Rectangle | Circle; // 联合
function area(s: ShapeType): number {
  switch (s.kind) {
    case "square": // 类型保护
      return Math.pow(s.size, 2);
      break;
    case "rectangle":
      return s.height * s.width;
      break;
    case "circle":
      return Math.PI * s.radius * 2;
      break;
    default:
      throw new Error("Unexpected object:" + s);
  }
}
const square: Square = {
  size: 10,
  kind: 'square',
}
console.log(square.kind, area(square));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 3、联合交叉类型

  • 优先级:& > |
type MType = number & 5 | string & 'abc';
const m: MType = 6;
const n: MType = 'def';
1
2
3

# 枚举

# 1、特点

  • 默认值从0开始为元素编号。分数字枚举、字符串枚举、异构枚举;
  • 允许值相同,优先取顺序较后的元素。

# 2、数字枚举

传键则返回值,传值则返回键

enum direct {
  up = 10,
  down,
  left,
  right
}
console.log(direct.right);
// 11是key,是对象属性访问,不要把它当作数组索引访问了
console.log(direct[11]);
1
2
3
4
5
6
7
8
9

# 3、字符串枚举

始终返回值

enum names {
  "Json",
  "Tom",
  "Jack"
}
console.log(names[1]);

enum strDirect {
  up = "toUp",
  down = "toDown",
  left = "toLeft",
  right = "toRight"
}
console.log(strDirect.down);
console.log(strDirect["left"]);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 4、异构枚举

数字枚举和字符串枚举的混合

enum result {
  success = 1,
  error = "err"
}
console.log(result[1]);
console.log(result["error"]);
1
2
3
4
5
6

# 5、枚举是字面量时需注意的点

切勿使用逻辑或进行类型判断,以下面为示例,

enum shape {
  ICircle,
  IRectangle,
  ISquare,
}
interface ICircle {
  kind: shape.ICircle
}
interface IRectangle {
  kind: shape.IRectangle
}
interface ISquare {
  kind: shape.ISquare
}
function func(x: shape) {
  // 条件总是true
  // 编译时会提示报错:This comparison appears to be unintentional because the types 'shape.ITest' and 'shape.ISquare' have no overlap.
  if (x !== shape.ICircle || x !== shape.ISquare) {
    console.log('exec code ...')
  }
}
func(shape.ISquare);
func(shape.ICircle);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 6、枚举成员

枚举成员分常量成员与计算成员。

常量成员,包括以下几种:

  • 数值或字符串字面量
  • 带圆括号的常量枚举表达式
  • 对先前定义的常量枚举成员的引用
  • 带有一元操作符(+-~)的常量枚举表达式
  • 带有二元操作符(+-*/%<<>>>>>&|^)的常量枚举表达式

计算成员:不符合常量成员条件的成员

示例如下,

enum demo {
  "abc",
  num = 6 + (5 & 5),
  zero = ~-1,
  num2 = num,
  len = "abc".length,
}
console.log(demo.abc);
console.log(demo.num);
console.log(demo[0]); // 存在相同值时,取顺序较后的
console.log(demo.num2);
console.log(demo.len);
1
2
3
4
5
6
7
8
9
10
11
12

# 7、常量枚举

编译后不生成枚举定义部分的代码。不能包含计算成员,否则编译时报错

const enum ConstantEnum {
  a = 1,
  b = a * 2,
  len = 'ab'.length,// 报错:const enum member initializers can only contain literal values and other computed enum values.
}
const refEnum = {
  a: ConstantEnum.a,
  b: ConstantEnum.b
}
// 上面代码编译后生成的代码如下:
// const refEnum = {
//     a: 1 /* ConstantEnum.a */,
//     b: 2 /* ConstantEnum.b */
// };
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 函数

# 1、函数类型

函数类型声明格式:(参数类型列表) => 返回值类型

示例如下,

type FunType = (a: string, b: number, c?: number) => number;
const add:FunType = function (x: string, y: number = 5) { 
  return +x + y
};
console.log(add('4', 10));
console.log(add('4', undefined));

type FunType2 = (a: string, ...rest: number[]) => number;
const add2:FunType2 = function (x: string, ...rest: number[]) {
  const sum: number = (rest || []).reduce((sum, value) => sum + value , 0) 
  return +x + sum
};
console.log(add2('4', 5, 6, 7));
1
2
3
4
5
6
7
8
9
10
11
12
13

从上面示例可以发现,

1、参数类型在顺序上要一一对应,参数名是否正确不影响

2、当参数设置了默认值,若想使用默认值,则传undefined

3、若参数是可选的,可以在参数名加?,且必须置于参数列表末

4、若可选参数有多个且不确定,可以使用剩余参数,可以认为是一个长度不定的可选参数数组

# 2、调用签名

作为函数被调用,且可以额外自定义属性。示例如下,

type FunType3 = {
  desc: string;
  (str: string): void;
}
function printf(str: string){
  console.log(str);
}
printf.desc = 'print';
const func3: FunType3 = printf;
console.log(func3.desc);
func3('hello')
1
2
3
4
5
6
7
8
9
10
11

# 3、构造签名

支持new创建函数对象

type FunType4 = {
  new (str: string): object;
  message: string;
}
class consFunc{
  static message:string = "";
  constructor(str: string){
    console.log("constructor");
    consFunc.message = str;
  }
}
const func4: FunType4 = consFunc;
const f = new func4('This is a function object');
console.log(consFunc.message);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

通过上面示例,发现定义的属性是挂载在类上的静态属性,若希望挂载在实例上,我们可以通过下面方式实现。

type FunType5 = {
  message: string;
}
type FunType6 = {
  new (str: string): FunType5;
}
const createInstance = (func: FunType6, str: string): FunType5 => {
  return new func(str);
}
class consFunc2 {
  message: string;
  constructor(str: string){
    console.log("constructor");
    this.message = str;
  }
}
const obj5: FunType5 = createInstance(consFunc2, "This is a function object");
console.log(obj5.message);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

若希望既支持函数调用,又支持new方式创建,类型定义示例如下,

type FuncOrNew = {
  (): void;
  new (): object;
}
1
2
3
4

# 4、函数参数中的this

必须在函数第一个形参定义this,注意函数不能是箭头函数

示例一:

// 当对全局对象挂载变量或方法时,若在全局文件中,在本文件声明即可,而若在模块文件中,要在指定路径下的存放类型定义文件中声明
//  --- 声明start ---
interface Window{
  func: (p:string) => void;
  myName: string;
}
//  --- 声明end ---

function func(this: Window, name:string):void{
  this.myName = name;
}
window.func = func;
window.func("I'm a global variable");
console.log(window.myName);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

示例二:

type K = {
  name: string, 
  func: (p:string) => void
};
// 定义时的this类型须与调用时的类型一致。若this指向不单一,可以使用联合类型
function func(this: K, name: string){
  this.name = name;
}
const l:K = {
  name: 'hello',
  func:(p:string) => {},
}
l.func = func;

l.func('hello world')
console.log(l.name);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 5、函数重载

  • 重载签名 函数同名,但形参列表的个数、类型、顺序不同。
function fn(num: number): void;
function fn(str: string): void;
1
2
  • 重载签名实现 根据传入不同参数,做不同处理。注意有一个以上重载签名,签名实现才有意义。
function fn(p: number | string): void{
  console.log(typeof p);
}
fn(1);
fn('welcome to study typescript')
1
2
3
4
5

# 接口

描述事物

# 1、属性修饰符

提供只读修饰符readonly和可选修饰符?

interface Person {
  age: number;
  // readonly表示只读
  readonly sex: string;
  // ?表示可选
  hobby?: string;
}
1
2
3
4
5
6
7

# 2、索引签名

格式:[attr_name:attr_type]:value_type

属性名不确定时使用。注意索引签名的值类型必须满足其他现有属性的所有值类型

顺便提及:一般对象字面量都会做属性检查。绕开属性检查的方式有赋值一变量、断言以及这里提到的索引签名。

interface Animal {
  age: number;
  sex: string;
  readonly eyes: number | boolean;
  readonly nose: number | boolean;
  readonly mouth: number | boolean;
  readonly hands: number | boolean;
  readonly legs: number | boolean;
  eatMeat?: string;
  // 索引签名
  [prop: string]: any;
}
function addAnimal(animal: Animal) {
  const { 
    eyes, 
    nose, 
    mouth, 
    hands, 
    legs 
  } = animal;
  console.log(
    `It has ${legs} legs,${eyes} eyes,${mouth} mouth,${nose} nose,and ${hands} hands.`
  );
  
  const arr = ["eyes", "legs", "nose", "mouth", "hands"];
  for (let item in animal) {
    if (arr.indexOf(item) === -1) {
      console.log(`more info:${item}:${animal[item]}`);
    }
  }
}
// 绕开属性检查——赋值一变量
const animal: Animal = {
  legs: 2,
  eyes: 2,
  mouth: 1,
  nose: 1,
  hands: 2,
  age: 27,
  sex: "male",
  a: 1
};
addAnimal(animal);
// 绕开属性检查——断言
addAnimal({
  legs: 2,
  eyes: 2,
  mouth: 1,
  nose: 1,
  hands: 2,
  age: 18,
  sex: "female",
  b: 2,
} as Animal);
// 绕开属性检查——索引签名
addAnimal({
  legs: 2,
  eyes: 2,
  mouth: 1,
  nose: 1,
  hands: 2,
  age: 50,
  sex: "male",
  c: 2,
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

# 3、接口继承

第一种情况,接口继承接口

interface Shape {
  color: string;
}
interface Square extends Shape {
  sideLength: number;
}
const square = <Square>{};
square.color = "blue";
square.sideLength = 10;
1
2
3
4
5
6
7
8
9

第二种情况,接口继承类。当一个接口继承一个类后,若另一个类要实现该接口,注意要继承该类,因为接口继承该类,只声明了成员,并未实现,所以需要由其子类实现

class A {
  age: number = 1000;
}
interface B extends A {
  getAge(): string;
}
class C extends A implements B {
  getAge(): string {
    return `年龄:${this.age}`;
  }
}
const cInstance = new C();
console.log(cInstance.getAge());
1
2
3
4
5
6
7
8
9
10
11
12
13

支持多继承,用逗号隔开

interface Monkey{
  sex: string;
  age: number;
}
interface Human{
  country: string;
  job: string;
}
interface YellowMan extends Human, Monkey{
  language: string; 
}
const p: YellowMan = {
  sex: 'male',
  age: 20,
  country: 'China',
  job: 'engineer',
  language: 'Chinese'
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 4、接口应用

  • 定义对象
interface IObj{
  name: string;
}
const obj: IObj = {
  name: 'typescript'
}
1
2
3
4
5
6
  • 定义函数
interface play {
  (time: number, who: string): string;
}
const myPlay: play = function(t: number, w: string): string {
  return `I spent ${t} hours playing with ${w}`;
};
console.log(myPlay(1, "John"));
1
2
3
4
5
6
7
  • 定义索引类型

分数字索引签名(键为数字)和字符串索引签名(键为字符串)两种

// 数字索引签名
interface NumberIdx{
  [idx:number]: number;
}
const arr: NumberIdx = [1,2,3];
console.log(arr[2]);

// 字符串索引签名
interface StringIdx {
  [index: string]: number;
}
const stringIdx: StringIdx = {
  "Hello World": 0,
  "Hello TypeScript": 1,
  "Hello Friend": 2
};
for (const idx in stringIdx) {
  console.log(`key:${idx},value:${stringIdx[idx]}.`);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  • 接口实现函数的构造签名

下面通过示例解读接口如何实现函数的构造签名

// 1、定义好接口静态部分,如属性和方法
interface IClock {
  now: Date;
  setTime(d: Date): void;
}
// 2、定义好接口动态部分,即构造函数
interface ClockConstructor {
  new (hour: number, minute: number): IClock;
}
// 3、定义创建实例的方法
const createClock = (
  cons: ClockConstructor,
  hour: number,
  minute: number
): IClock => {
  return new cons(hour, minute);
};
// 4、实现接口的类
class DigitalClock implements IClock {
  now: Date = new Date();
  constructor(hour: number, minute: number) {
    this.now.setHours(hour);
    this.now.setMinutes(minute);
  }
  setTime(d: Date) {
    this.now = d;
  }
}
// 5、给创建实例方法传入相应参数,创建实例
const digital:IClock = createClock(DigitalClock, 11, 11);
console.log(digital.now);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  • 接口实现函数的调用签名

解读示例如下,

// 1、定义接口
interface Counter {
  (start: number): string;
  interval: number;
  reset(): void;
}
// 2、定义获取实现接口函数的方法
function getCounter(): Counter {
  const counter = (function(start: number) {
    console.log("start:", start);
  }) as Counter;
  counter.interval = 10;
  counter.reset = () => {
    counter.interval = 0;
  };
  return counter;
}
// 3、获取函数并调用
const c = getCounter()
c(100);
// 4、获取函数的属性或方法
console.log(c.interval);
c.reset();
console.log(c.interval);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

#

事物的具体实例。

提示:TS内部实现了在定义类时,也自动定义了同名接口。

# 1、成员可见性修饰符

接口部分介绍的属性修饰符和索引签名在类中同样试用。这里的成员可见性修饰符在接口亦适用。

  • public:成员可见性默认值。访问无限制

  • protected:protected成员在自身及其派生类中可以访问

class Developer{
  protected skill!:string;
  protected constructor(){}
}
class FrontEndDeveloper extends Developer{
  // 可以在子类修改成员可见性,暴露受保护成员
  skill:string = 'html,css,javascript'
  constructor(){
    super();
  }
}
// 当构造函数被标记protected,意味该类不能直接被实例化,但是能通过子类实例化
const developer = new Developer();//  Constructor of class 'Developer' is protected and only accessible within the class declaration.
const feDeveloper = new FrontDeveloper();
console.log(`The front end developer is good at ${feDeveloper.skill}`);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  • private:private成员只在自身类中可以访问

示例一解读:

class Girlfriend {
  private girlFriendName: string = "";

  // 通过存取器实现在外部修改私有成员的值
  get name(): string {
    return this.girlFriendName;
  }

  set name(girlFriendName: string) {
    this.girlFriendName = girlFriendName;
  }
}
const myGirlFriend = new Girlfriend();
myGirlFriend.name = "jiuLing";
1
2
3
4
5
6
7
8
9
10
11
12
13
14

示例二解读:

class D {
  private x = 10;
  public sameAs(other: D) {
    // 与其他面向对象编程语言不同,允许访问同个类不同实例的私有成员
    return other.x === this.x;
  }
}
const d = new D();
// 运行时,通过in关键字或遍历简单属性,即使是受保护、私有的成员仍能被访问
console.log('x' in d);
console.log(Object.getOwnPropertyNames(d));
1
2
3
4
5
6
7
8
9
10
11

# 2、继承类

  • 类不能有未初始化的属性或未实现的方法,除非字段后加!修饰,则可不必初始化。
  • 当父类属性是可选属性,子类实例想获取该属性时,子类需要重新声明该属性,否则获取到的是undefined。
class Base {
  desc!: string;
  // 若count是可选属性,this.count是undefined
  // count?: number = 0;
  count: number = 0;
  print(msg: string):void {
    console.log(msg);
    this.count++;
  }
}
class Derived extends Base {
  // 属性覆盖
  desc: string = "I'm a derived class";

  constructor() {
    // 当继承某个基类,通过this调用成员前必须调用父类构造函数super();
    super();
    console.log(this.count);    
  }

  // 方法重写
  print(msg: string){
    super.print(msg);
    console.log(`current count: ${this.count}`);
  }

  // 类同接口一样,支持索引签名
  [s: string]: boolean | ((s: string) => boolean | void) | string | number;

  check(s: string) {
    return this[s] as boolean;
  }
}
const derived = new Derived();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
  • 声明类型
interface Canis {
  dateOfBirth: any;
}
interface Dog extends Canis {
  breed: any;
}
class AnimalHouse {
  resident: Canis;
  constructor(animal: Canis) {
    this.resident = animal;
  }
}
class DogHouse extends AnimalHouse {
  // Does not emit JavaScript code, only ensures the types are correct
  declare resident: Dog;
  constructor(dog: Dog) {
    super(dog);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 3、实现接口

interface Pingable {
  ping(): void;
}
class Sonar implements Pingable {
  ping() {
    console.log("ping!");
  }
}
1
2
3
4
5
6
7
8

支持多接口实现,逗号隔开即可

interface Monkey2{
  sex?: string;
  age: number;
}
interface Human2{
  country: string;
  job: string;
}
class YellowMan2 implements Human2, Monkey2{
  language!: string; 
  age!: number;
  country!: string;
  job!: string;
}
const ym = new YellowMan2();
// 同extends,实现有可选属性的接口不会自动创建可选属性
ym.sex = 'male';// 报错:Property 'sex' does not exist on type 'YellowMan2'.
ym.age = 20;
ym.country = 'China';
ym.job = 'engineer';
ym.language = 'Chinese';
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 4、成员静态修饰符static

当允许同一类下的不同对象共享时应用,减少动态分配。被其修饰的属性无法通过实例对象修改。

注意:静态成员不能有name、length、call,因为它们在函数内部已被使用了

class Apple {
  static color: string = "red";
  // 静态代码块在类加载时执行
  static {
    console.log('This is a static block.');
  }
  constructor() {
    console.log("All we know,Apple is", Apple.color);
  }
}
const apple1 = new Apple();
const apple2 = new Apple();
Apple.color = "green";
const apple3 = new Apple();
const apple4 = new Apple();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 5、类抽象修饰符abstract

  • 可作为基类使用,但不可实例化,却不同于接口,可以有已实现的方法。
  • 继承抽象类时必须实现抽象属性和方法。
  • 向上转型:只能调用与父类共有方法,子类特有方法不能被调用。

示例如下,

abstract class Fruit {
  color!: string;
  shape!: string;

  abstract getShapeAndColor(): void;

  health() {
    console.log("Fruit is healthy.");
  }
}
class WaterMelon extends Fruit {
  shape: string = "ellipse";

  constructor() {
    super();
    this.color = "green";
  }

  getShapeAndColor(): void {
    console.log(`The watermelon is ${this.shape} and ${this.color}`);
  }

  detail():void {
    console.log("The watermelon is quenched one's thirst in the summer.")
  }

}
const waterMelon: Fruit = new WaterMelon();
waterMelon.getShapeAndColor();
waterMelon.health();
waterMelon.detail();// Property 'detail' does not exist on type 'Fruit'.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  • 抽象构造签名

示例解读:

function getFruit(ctor: new () => Fruit) {
  const instance = new ctor();
  instance.getShapeAndColor();
}
getFruit(Fruit);//报错: Argument of type 'typeof Fruit' is not assignable to parameter of type 'new () => Fruit'. Cannot assign an abstract constructor type to a non-abstract constructor type.

getFruit(WaterMelon);
1
2
3
4
5
6
7

# 6、类的解构赋值

仅属性能被解构,方法不能。

class myClass {
  time = "2020-03-03";
  count() {}
}
const clone = {
  ...new myClass()
};
console.log(clone);
1
2
3
4
5
6
7
8