TypeScript相比JavaScript的优势

  1. ts是js的超集,存在类型的脚本语言
  2. 强大的类型系统,拥有静态类型检查能力
  3. 新增类型注解和类型推断
  4. 拥有丰富的class扩展功能
  5. 编译期进行类型检查
  6. 开发环境能提供丰富的信息
  7. 大部分检查有语言自身完成

基础类型和对象类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 基础类型 number string boolean void null undefined symbol
const count: number = 123;
const studentName: string = "neo";

// 数组类型
const numbers: number[] = [1, 2, 3];

// 对象类型 {} Class function []
type Student = {
name: string,
age: number
}

const student: Student = {
name: "neo",
age: 188
}

// 函数类型
const getTotal: () => number = () => {
return 123;
}

类型注解和类型推断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 类型注解 type annotation 告诉TS变量是什么类型

type myType = string | number

const one: myType = 1
const two: myType = '2'

// 类型推断 type inference TS会自动尝试分析变量的类型

let three = 3 // ts会自动推断为number类型

function add(first, second) { // ts会自动推断出first和second的类型为any,因为没有定义类型,ts不知道外部传入参数是什么类型
return first + second;
}

const total = add(1, 2); // ts会自动推断出total的类型为any,因为add函数没有定义返回值类型

函数类型需要对返回值进行约束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function add(first: number, second: number) {
return first + second + ''; // 假设数据处理出错
}

const total = add(1, 2); // ts会推断total的类型为string

// ---------------限制返回值类型
function add(first: number, second: number): number {
return first + second + ''; // ts会报错,不能将类型“string”分配给类型“number”。ts(2322)
}

const total = add(1, 2); // ts会推断total的类型为string


// ---------------函数形参解构需要精确定义类型
const hello = ({ name, age }: { name: string; age: number }): string => {
return `hello ${name}, ${age} years old`;
}

const res = hello({ name: 'xiuji', age: 18 });

数组与元组

使用注解规定对应变量/常量的数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 数组
const strArr: string[] = ['a', 'b', 'c']
const numArr: number[] = [1, 2, 3]
const undefinedArr: undefined[] = [undefined, undefined]

// 数组中对象类型的定义
const objArr: { name: string, age: number }[] = [
{ name: 'neo', age: 18 },
]

// 类型别名 type alias
type User = { name: string, age: number }
const objArr2: User[] = [{ name: 'neo', age: 18 }]

// 元组 tuple
const tuple: [string, number, boolean] = ['a', 1, false]

// 例如csv csv是一种文件格式,逗号分隔值(Comma-Separated Values,CSV,有时也称为字符分隔值,因为分隔字符也可以不是逗号。在本文中,指逗号)文件是一种电子表格文件,其中的数据由逗号分隔。
const csvArr: [string, number][] = [['a', 1], ['b', 2], ['c', 3]]

interface接口

接口定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface Person {
name: string;
age: number;
}

type Person1 = {
name: string;
}

const setPersonName = (person: Person, name: string) => {
person.name = name;
}

const getPersonName = (person: Person1) => {
console.log(person.name);
}

const person = {
name: 'neo'
}

setPersonName(person, 'neo1'); // 类型 "{ name: string; }" 中缺少属性 "age",但类型 "Person" 中需要该属性。
getPersonName(person);

接口中定义了对应类型,使用接口的地方定义的变量或字面量必须包含接口中所有类型

接口校验

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
interface Person {
// readonly name: string; ----readonly,只读属性,不可进行赋值类操作
name: string;
age?: number; // 使用?标注该属性为选填属性
}

const setPersonName = (person: Person, name: string): void => {
person.name = name;
}

const getPersonName = (person: Person): void => {
console.log(person.name);
}

const person = {
name: 'neo',
sex: 'male',
}

// ---------------
getPersonName({
name: 'neo',
sex: 'male',
}); // 提示类型“{ name: string; sex: string; }”的参数不能赋给类型“Person”的参数。
对象字面量只能指定已知属性,并且“sex”不在类型“Person”中
getPersonName(person); // 无报错

setPersonName(person, 'neo1');

使用字面量形式传参,ts会对字面量进行强校验。使用变量形式传参,ts会对变量进行弱校验。

1
2
3
4
5
6
7
8
9
10
interface Person {
name: string;
age?: number;
[propName: string]: any; // 接收任意属性
}

getPersonName({
name: 'neo',
sex: 'male', // 接口中没有定义对应属性,此时没有报错
});

接口中还可以定义方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Person {
name:string;
age?:number;
[propName: string]: any; // 接收任意属性
say(): string; // 函数类型
}

const person = {
name: 'neo',
sex: 'male',
say() {
return 'hello world'; // 必须有对应属性的返回值
}
}

implements和extends关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
// implements关键字 用来定义类需要实现的接口,一个新的类,从父类或者接口实现所有的属性和方法,同时可以重写属性和方法,包含一些新的功能
class User implements Person {
name = 'terminal';
say() {
return 'im bak';
}
}

// extends关键字 一个新的接口或者类,从父类或者接口继承所有的属性和方法,不可以重写属性,但可以重写方法
interface Son extends Person {
// 包含Person的所有属性和方法
cry(): boolean;
}

type 和 interface 的区别

type关键字是声明类型别名的关键字。它的语法如下:

1
type AliasName = Type;
  • type:声明类型别名的关键字
  • AliasName:类型别名的名称
  • Type:类型别名关联的具体类型

通过关键字 interface可以定义一个接口类型。它能合并众多类型声明至一个类型声明。

接口声明只存在于编译阶段,在编译后的JS代码中不包含任何接口代码

语法如下:

1
2
3
4
5
interface InterfaceName {
TypeMember;
TypeMember;
...
}
  • interface:定义接口的关键字
  • InterfaceName:接口名,首字母需要大写
  • TypeMember:接口的类型成员

不同点

  • type 在声明类型别名之后实际上是一个赋值操作,它需要将别名与类型关联起来。也就是说类型别名不会创建出一种新的类型,它只是给已有类型命名并直接进行引用。interface定义了一个接口类型。
  • type 能够表示非对象类型,interface只能表示对象类型
  • interface可以继承其他的接口、类等对象类型, type 不支持继承。

类型别名可以借助交叉类型来实现继承的效果。而这种方法也只适用于表示对象类型的类型别名,对于非对象类型是无法使用的。

1
2
3
4
5
6
7
8
type Football = { name: string }

type Basketball = Football & { ball: number }

function foo(basketball: Basketball) {
const name = basketball.name
const radius = basketball.radius
}
  • interface接口名总是会直接显示在编译器的诊断信息和代码编辑器的智能提示中,而 type 的名字只在特定情况下才会显示出来——只有当类型别名表示数组类型、元组类型以及类或者接口的泛型实例类型时才展示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 联合类型
type NumberOrBigint = number | bigint;
// 接口类型
interface ItemNum {
count: number;
}

function f(itemNum: ItemNum, value: NumberOrBigint) {
const ball: number = value;
// ts语法校验报错不能将类型“NumberOrBigint”分配给类型“number”。不能将类型“bigint”分配给类型“number”
// 这是因为联合类型"NumberOrBigint"包括了"number"和"bigint"两种可能的类型,而在将"value"赋值给"ball"时,将其声明为"number"类型。由于"bigint"不是"number"的子类型,因此会引发类型错误。要解决这个问题,可以将"ball"的类型声明为"NumberOrBigint",以接受"number"和"bigint"类型的值
const coffee: number = itemNum;
// 不能将类型“ItemNum”分配给类型“number”
// 在将"itemNum"赋值给"coffee"时,将其声明为"number"类型。然而,"itemNum"是一个接口类型,它包含一个名为"count"的"number"属性。需要访问"itemNum.count"来获取数值,然后将其赋给"coffee"。修复这个问题的方法是将"coffee"的类型声明为"number",然后将"itemNum.count"的值赋给它
}

类的定义与继承

定义

1
2
3
4
5
6
7
8
9
10
11
12
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
run(): string {
return `${this.name} is running`
}
}

const snake = new Animal('lily'); // 实例化类
console.log(snake.run()); // lily is running

继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 继承
class Dog extends Animal {
bark(): string {
return `${this.name} is barking`
}
// 重写父类的方法
run(): string {
// super 调用父类的方法
return 'dog ' + super.run()
}
}

const dog = new Dog('dog');
console.log(dog.run()); // dog dog is running

类中的访问类型和构造器

类中有public、private、protected修饰符

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
class Father {
// public 公有属性或方法,可以在任何地方被访问到(类内外都可调用),默认所有的属性和方法都是public
public name: string;
// private 私有属性或方法,不能在声明它的类的外部访问
private age: number;
// protected 受保护的属性或方法,和private类似,区别是它在子类中也是允许被访问的
protected occupation: string;
// constructor 构造函数,每次实例化时都会被调用一次,用于初始化类的成员变量
constructor(name: string, age: number, occupation: string) {
this.name = name;
this.age = age;
this.occupation = occupation;
};
say(): void {
console.log(this.name);
console.log(this.age);
}
}

const father = new Father("xiuji's daddy", 998, 'coder')
console.log(father.name) // xiuji's daddy
console.log(father.age) // 属性“age”为私有属性,只能在类“Father”中访问。ts(2341);
console.log(father.occupation) // 属性“occupation”受保护,只能在类“Father”及其子类中访问。ts(2445);

class Son extends Father {
constructor(public age: number) {
// super 关键字只能在子类的构造函数中使用,用于调用父类的构造函数。它的目的是确保子类在创建实例时能够正确地初始化继承自父类的属性
super("xiuji's daddy", 988, 'coder')
}
wishToBe(): void {
console.log(this.occupation); // protected 受保护的属性或方法,和private类似,区别是它在子类中也是允许被访问的
}
}

const son = new Son()
console.log(son.wishToBe()) // coder

constructor简化写法:不定义属性名,通过constructor(public 属性名: 类型)的形式定义

1
2
3
4
5
6
7
8
9
class Father {
// 传统写法
public name: string;
constructor(name: string) {
this.name = name;
}
// 简化写法
constructor(public name: string) { }
}

**ps:**子类继承父类,即使父类中没有属性,子类中也需要调用super

1
2
3
4
5
6
7
8
9
class Father {
}

class Son extends Father {
constructor(public age: number) {
// ts提示派生类的构造函数必须包含 "super" 调用。ts(2377)
// super
}
}

静态属性、Setter和Getter

实现单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 单例模式,一个类只能被实例化一次
class Demo {
private static instance: Demo;
private constructor(public name: string) {}
// static静态方法,将方法挂载到类上,而不是实例上
static getInstance(name: string) {
if (!this.instance) {
this.instance = new Demo(name)
}
return this.instance
}
}

const demo1 = Demo.getInstance('snake')
const demo2 = Demo.getInstance('snake1')
console.log(demo1.name); // snake
console.log(demo2.name); // snake 两次传参有区别,打印的name只会是第一次传进类中的name,说明这个类只被实例话一次

Setter和Getter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Demo {
constructor(private _name: string) { }
get name() {
// 处理私有属性——保护私有属性,类似加密的过程
const relaName = this._name + new Date().getTime()
return relaName
}
set name(name: string) {
const relaName = name.split(' ')[0]
this._name = relaName
}
}
const demo = new Demo('snake')
console.log(demo.name); // snake1689669543435
demo.name = 'snakeA xiuji'
console.log(demo.name); // snakeA1689669543436