# 基础用法

# 静态类型

/** count
 * 不可以为其他类型
 * 编辑器会提示数字类型的所有方法
 */
const count: number = 2021

# 基础类型和对象类型

基础类型: boolean, number, string, void, undefined, symbol, null 
对象类型: {}, Class, function, []

// 对象类型
class Person {}

const teacher: {
  name: string
  age: number
} = {
  name: 'Zws',
  age: 18
}

const numbers: number[] = [1, 2, 3]

const zws: Person = new Person()

// 对象类型-函数的两种写法儿
// 第一种(可以忽略 number,类型推断会推断出返回值是number)
const func = (str: string): number => {
  return parseInt(str, 10)
}
// 第二种(可以理解为:冒号后面跟的是函数的类型,等号后面是函数体。不能忽略number,不然语法错误)
const func1: (str: string) => number = str => {
  return parseInt(str, 10)
}

# 类型注解和类型推断

类型推断(type inference):TS  会自动的去尝试分析变量的类型。例如:

// 这就是典型的类型推断,它们的类型是 number 而且值永远都不会变的
const firstnumber = 1
const secondNumber = 2
const total = firstNumber + secondNumber

类型注解(type annotation) :告诉  TS  变量是什么类型。例如:

// 当 TS 无法推断出变量类型的时候需要添加类型注解
function getTotal(firstNumber: number, secondNumber: number) {
  return firstNumber + secondNumber
}
const total = getTotal(1, 2)

// 其他的情况
interface Person {
  name: string
}
const rawData = '{"name": "zws"}'
const newData: Person = JSON.parse(rawData)

// 一个变量是一个数字类型,后续要变成字符串。类似或运算符
let temp: number | string = 123
temp = '456'

# 函数相关类型

// void类型:没有返回值
function sayHello(): void {
  console.log('hello')
}
// never类型:函数执行不完
function errorEmitter(): never {
  while (true) {}
}
// 函数的解构赋值
function add({ first, second }: { first: number; second: number }): number {
  return first + second
}
add({ first: 1, second: 2 })

# 数组和元组

# 数组

// 数组可以是数字和字符串类型
const numberArr: (string | number)[] = [1, 2, 3]
// undefined 数组
const undefinedArr: undefined[] = [undefined, undefined, undefined]

// 存储对象类型的内容
const objectArr: { name: string; age: number }[] = [
  {
    name: 'zws',
    age: 18
  }
]
// 使用类型别名(type alias)
type User = { name: string; age: number }
// 存储对象类型的内容
const objectArr: User[] = [
  {
    name: 'zws',
    age: 18
  }
]

// 关于 Class
class Teacher {
  name: string
  age: number
}
const objectArr: Teacher[] = [
  new Teacher(),
  {
    name: 'zws',
    age: 18
  }
]
  1. 添加和删除元素的方法

push(): 在数组末尾添加一个或多个元素,返回数组的新长度。

let arr = [1, 2];
arr.push(3);  // [1, 2, 3]

pop(): 删除数组最后一个元素,返回被删除的元素。

let arr = [1, 2, 3];
let last = arr.pop();  // [1, 2], last = 3

unshift(): 在数组的开头添加一个或多个元素,返回数组的新长度。

let arr = [1, 2];
arr.unshift(0);  // [0, 1, 2]

shift(): 删除数组的第一个元素,返回被删除的元素。


let arr = [1, 2, 3];
let first = arr.shift();  // [2, 3], first = 1

splice(): 用于删除、替换或添加元素。可以删除或插入指定位置的元素。

let arr = [1, 2, 3, 4];
arr.splice(1, 2, 'a', 'b');  // [1, 'a', 'b', 4],从索引 1 开始,删除 2 个元素,插入 'a', 'b'
  1. 查找和访问元素的方法

indexOf(): 查找指定元素在数组中的第一个出现的位置,返回索引。如果没有找到则返回 -1。

let arr = [1, 2, 3, 2];
let index = arr.indexOf(2);  // 1

lastIndexOf(): 查找指定元素在数组中的最后一次出现的位置。

let arr = [1, 2, 3, 2];
let lastIndex = arr.lastIndexOf(2);  // 3

includes(): 判断数组是否包含某个元素,返回 true 或 false。

let arr = [1, 2, 3];
arr.includes(2);  // true

find(): 返回数组中第一个满足测试函数的元素。如果没有找到则返回 undefined。

let arr = [1, 2, 3, 4];
let found = arr.find(el => el > 2);  // 3

findIndex(): 返回数组中第一个满足测试函数的元素的索引。如果没有找到则返回 -1。

let arr = [1, 2, 3, 4];
let index = arr.findIndex(el => el > 2);  // 2
  1. 遍历数组的方法

forEach(): 对数组的每个元素执行一次提供的函数,常用于遍历数组,但不会返回值。

let arr = [1, 2, 3];
arr.forEach(el => console.log(el));  // 打印 1, 2, 3

map(): 创建一个新数组,其结果是对原数组中的每个元素调用提供的函数的返回值。

let arr = [1, 2, 3];
let result = arr.map(el => el * 2);  // [2, 4, 6]

filter(): 创建一个新数组,包含所有通过测试函数的元素。


let arr = [1, 2, 3, 4];
let result = arr.filter(el => el > 2);  // [3, 4]

reduce(): 对数组中的每个元素执行一个累积函数,从而将数组缩减为单个值。


let arr = [1, 2, 3, 4];
let sum = arr.reduce((acc, cur) => acc + cur, 0);  // 10

reduceRight(): 和 reduce() 类似,不过从数组的末尾开始执行。

let arr = [1, 2, 3, 4];
let sum = arr.reduceRight((acc, cur) => acc + cur, 0);  // 10
  1. 数组排序与反转

sort(): 对数组中的元素进行排序。默认按照字符顺序,数字排序需要提供比较函数。


let arr = [3, 1, 4, 2];
arr.sort();  // [1, 2, 3, 4] (默认按字符顺序)

// 数字排序
arr.sort((a, b) => a - b);  // [1, 2, 3, 4]

reverse(): 反转数组中的元素。

let arr = [1, 2, 3];
arr.reverse();  // [3, 2, 1]
  1. 数组转换 concat(): 合并两个或多个数组,返回一个新数组。

let arr1 = [1, 2];
let arr2 = [3, 4];
let newArr = arr1.concat(arr2);  // [1, 2, 3, 4]

join(): 将数组的所有元素连接成一个字符串,并返回这个字符串。

let arr = [1, 2, 3];
let str = arr.join('-');  // "1-2-3"

slice(): 返回数组的一个子集,不修改原数组。

let arr = [1, 2, 3, 4];
let subArr = arr.slice(1, 3);  // [2, 3]

toString(): 返回数组的字符串形式。


let arr = [1, 2, 3];
let str = arr.toString();  // "1,2,3"

flat(): 将多维数组 “拉平” 为一维数组。

let arr = [1, [2, [3, 4]]];
let flatArr = arr.flat(2);  // [1, 2, 3, 4]
  1. 检测数组

Array.isArray(): 用于判断一个值是否为数组。

Array.isArray([1, 2, 3]);  // true
Array.isArray('abc');  // false
  1. 填充与复制

fill(): 用静态值填充数组的一部分。

let arr = [1, 2, 3, 4];
arr.fill(0, 1, 3);  // [1, 0, 0, 4],从索引 1 开始,到索引 3 之前,用 0 填充

copyWithin(): 在当前数组内部复制数组的一部分到另一个位置。

let arr = [1, 2, 3, 4];
arr.copyWithin(1, 2);  // [1, 3, 4, 4]

# 元组 tuple

数组长度和类型都固定的情况下,可以使用元组进行管理

const teacherInfo: [string, string, number] = ['zws', 'jon', 18]

const teacherList: [string, string, number][] = [
  ['zws', 'jon', 18],
  ['sun', 'dea', 22],
  ['za2', 'wall', 26]
]

# Interface 接口

关于 Interface 代码 (opens new window)

TIP

interface  和  type  相类似,但不完全一致,type 可以校验基础类型,而 Interface 不支持基础类型的校验。

TS 里,能使用 Interface 的话就使用 Interface。

  • 接口只是 TS 帮助我们校验的工具,并不会变成 JS
  • 属性前加上?,代表该变量可有可无
  • 属性前加上 readonly,代表只读不可修改
interface Pereson {
  name: string
  age?: number
  readonly test: string
}
const getPersonName = (person: Pereson) => {
  console.log(person.name)
}

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

const person = {
  name: 'zws',
  age: 18,
  sex: 'male'
}

getPersonName(person)
setPersonName(person, 'lin')
  • 如果以字面量的形式传给函数,TS 会进行强校验。例如:
interface Pereson {
  name: string
  age?: number
}
const getPersonName = (person: Pereson) => {
  console.log(person.name)
}

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

const person = {
  name: 'zws',
  age: 18,
  sex: 'male'
}

getPersonName({
  name: 'zws',
  age: 18,
  sex: 'male'
}) // 会报错,sex不在 Person 种
setPersonName(person, 'lin')

// 修改 Interface 解决(最后一行代表的是,可以是任何字符串类型的键,任何值)
interface Pereson {
  name: string
  age?: number
  [propName: string]: any
}
  • Interface 里支持方法的写入
interface Pereson {
  name: string
  age?: number
  say(): string
}
const person = {
  name: 'zws',
  say() {
    return 'say hello'
  }
}
  • class 类应用接口
interface Pereson {
  name: string
  age?: number
  say(): string
}
// 语法 implements
class User implements Pereson {
  name = 'zws'
  say() {
    return 'say hello'
  }
}
  • 接口之间互相继承
// 关键字 extends
interface Teacher extends Pereson {}
  • 接口定义函数
interface SayHi {
  (word: string): string
}
const say: SayHi = (word: string) => {
  return word
}

interface 小案例, 公用属性使用 Interface 进行扩展:

interface Person {
  name: string
}
interface Teacher extends Person {}
interface Student extends Person {
  age: number
}
const teacher = {
  name: 'zws'
}
const student = {
  name: 'has',
  age: 18
}
const getUserInfo = (user: Person) => {
  console.log(user.name)
}
getUserInfo(teacher)
getUserInfo(student)

# class 类

# 类的定义与继承

关于类的定义与继承代码 (opens new window)

// 类里写属性与方法
class Person {
  name = 'zws'
  getName() {
    return this.name
  }
}
const person = new Person()
console.log(person.getName()) // zws

// 继承类,继承类属于字类,被继承的属于父类
class Teacher extends Person {
  getTeacherName() {
    return 'zws Teacher'
  }
  // 子类可以重写父类的属性与方法
  getName() {
    // super 关键字指向了父类,可以直接调用父类。不会受到类重写的影响
    return super.getName() + 'TTT'
  }
}

const teacher = new Teacher()
console.log(teacher.getName()) // zws
console.log(teacher.getTeacherName()) // zws Teacher

# 类的访问类型

访回类型:

  • private:允许在类内使用
  • protected:允许在类内及继承的子类中使用
  • public:允许在类的内外调用(默认)

自带方法:

  • readonly:只读属性
  • static:将方法挂载到类上而不是实例上

TIP

直接写在类里的属性或函数,相当于前面加了 public

class Person {
  protected name: string = '123'
  private age: number = 10
  public sayHi() {
    console.log('hi' + this.age)
  }
}
class Teacher extends Person {
  public sayBye() {
    return this.name
  }
}
const person = new Person()
person.sayHi()
const teacher = new Teacher()
console.log(teacher.sayBye())

# 构造器 constructor

constructor 会在 new 实例的时候自动执行

// 以下两段代码相同, constructor 里,参数前加上public代表在之前已经声明过这个变量了
// 传统写法
class Person {
  public name: string
  constructor(name: string) {
    this.name = name
  }
}
const person = new Person('zws')
// 简化写法
class Person {
  // public name: string
  constructor(public name: string) {
    // this.name = name
  }
}
const person = new Person('zws')
console.log(person.name)

字类集成父类并使用 constructor 的话,必须先调用父类的 constructor ,并按照父类的参数规则进行

// super()代表调用父类的 constructor
// 如果父类没有使用constructor 字类需要调用一个空的super()
class Person {
  constructor(public name: string) {}
}

class Teacher extends Person {
  constructor(public age: number) {
    super('zws')
  }
}

const teacher = new Teacher(28)

# 静态属性,Setter 和 Getter

Getter:读

Setter:写

// 可以通过getter访问私有属性,通过setter更改私有属性
// 一般用于对数据的加密
class Person {
  constructor(private _name: string) {}
  get name() {
    return this._name + ' has'
  }
  set name(name: string) {
    const realName = name.split(' ')[0]
    this._name = realName
  }
}
const person = new Person('zws')
console.log(person.name)
person.name = 'zwsa has'
console.log(person.name)

# 做个小案例

通过 TS 创建一个 Demo 类,这个类只能被调用一次 思路:

  • 不能在外部以 new Demo 的形式创建一个实例(将 constructor 设置为私有属性)
  • 使用 static (将方法挂载到类上而不是实例上)来实现
  • 使用 instance 方法来保存传入的值,并判断
class Demo {
  private constructor(public name: string) {}

  private static instance: Demo
  static getInstance(name: string) {
    if (!this.instance) {
      this.instance = new Demo(name)
    }
    return this.instance
  }
}
const demo1 = Demo.getInstance('zws')
const demo2 = Demo.getInstance('zwsa')
console.log(demo1.name)
console.log(demo2.name)

# 抽象类

  • 只能被继承,不能实例化
  • 抽象类里的抽象方法,不能够写具体实现
abstract class Gemo {
  width: number
  getType() {
    return 'Gemo'
  }
  abstract getArea(): number
}
class Cricle extends Gemo {
  // 子类继承了抽象类,里面的抽象方法必须实现一下
  getArea() {
    return 123
  }
}
class Square {}
class Triangle {}

# Enum 枚举

你可以使用常量const描述某种不会改变的值。但某些值是在一定范围内的一系列常量,如星期(周一~周日)、三原色(红黄蓝)、方位(东南西北)等,这种类型的值称为枚举

# 数值枚举

使用关键字 enum 来声明一个枚举类型数据。

TIP

枚举成员默认为数值类型

enum Direction {
  Up,
  Down,
  Left,
  Right
}

console.log(Direction.Up) // 0
console.log(Direction[0]) // "Up"

未赋初始值的枚举项会接着上个项的值进行递增。

使用 tsc 将上述代码编译为 js 后,可以发现 ts 使用 Direction[(Direction["Up"] = 0)] = "Up" 这样的内部实现对对象成员进行了双向的赋值。

由此可以看出,enum 具有双向映射的特点

var Direction
;(function (Direction) {
  Direction[(Direction['Up'] = 0)] = 'Up'
  Direction[(Direction['Down'] = 1)] = 'Down'
  Direction[(Direction['Left'] = 2)] = 'Left'
  Direction[(Direction['Right'] = 3)] = 'Right'
})(Direction || (Direction = {}))

console.log(Direction.Up) // 0
console.log(Direction[0]) // "Up"

# 字符串枚举

枚举成员为字符串时,其之后的成员也必须是字符串。

enum Direction {
  Up, // 未赋值,默认为0
  Down = '南',
  Left = '西',
  Right = '东'
}

# 常量枚举

你可以使用constenum关键字组合,声明一个常量枚举。 常量枚举中不允许使用计算值(变量或表达式)

image-20210503183210347

# Generics 泛型

泛型指的是,在定义函数、接口或类的时候不预先指定数据类型,而在使用时再指定类型的特性。

泛型可以提升应用的可重用性,如使用其创建组件,则可以使组件可以支持多种数据类型。

假如需要一个函数,返回传入它的内容 不使用泛型时,它会是这样的:

function echo(arg: any) {
  return arg
}

但这样就丢失了一些信息:传入的类型与返回的类型应该是相同的。如果我们传入一个数字,我们只知道任何类型的值都有可能被返回。于是此时需要一种方法使返回值的类型与传入参数的类型是相同的。

# 类型变量

这是一种特殊的变量,只用于表示类型而不是值。 使用泛型改写上述函数:

// 类型变量也遵循标识符定义规范,写为T只是习惯上这么做
function echo<T>(arg: T): T {
  return arg
}

这样,传入实参时,会同时将实参的类型传递给类型变量 T,同时返回值也是 T。

场景:交换两个数组元素

不使用泛型会丢失数据类型:

function swap(tuple) {
  return [tuple[1], tuple[0]]
}

使用泛型后,不仅会保有类型推断,还可以直接调用实例的方法:

function swapGeneric<T, U>(tuple: [T, U]): [U, T] {
  return [tuple[1], tuple[0]]
}

const result2 = swapGeneric(['string', 0.123])

// ts的类型推断系统能够明确得知第一个元素会是数值,而第二个元素会是字符串
result2[0].toFixed(2)
result2[1].toLocaleUpperCase()

# 约束泛型

假设有这样一个场景: 有时想操作某类型的一组值,并且我们知道这组值具有什么样的属性。下例中,我们想访问 arg 的 length 属性,但是编译器并不能推断每次传入的参数都有 length 属性,于是就报错了。

function echoWithArray<T>(arg: T): T {
  console.log(arg.length) // Error: T doesn't have .length
  return arg
}

经过不充分的考虑后,我们尝试将类型变量指定为数组:

function echoWithArray<T>(arg: T[]): T[] {
  console.log(arg.length)
  return arg
}

此时你也许会发现,这样只能传入数组,不能传入同样具有 length 属性的字符串、类数组对象等。

我们可以定义一个接口来描述约束条件,然后由类型变量继承此接口实现泛型约束:

// 一些编程规范约定接口以大写 I 作为前缀
interface IWithLength {
  length: number
}
function echoWithLength<T extends IWithLength>(arg: T): T {
  console.log(arg.length)
  return arg
}

// 只要包含 length 属性且为数值 均不会报错
const str1 = echoWithLength('str')
const obj = echoWithLength({ length: 10, width: 10 })
const arr2 = echoWithLength([])

# 泛型类

场景:定义一个类,能实现被 push 入的队列元素与 pop 出的元素的类型一致。

class Queue<T> {
  private data = []
  push(item: T) {
    return this.data.push(item)
  }
  pop(): T {
    return this.data.pop()
  }
}

//泛型类实例化时要指定具体的类型
const queue = new Queue<number>()
queue.push(1)
queue.push('str') // Error: 类型“string”的参数不能赋给类型“number”的参数。

# 泛型接口

万能的泛型同样可以用来描述接口:

interface KeyPair<T, U> {
  key: T
  value: U
}

// 泛型接口描述的对象,同样需要满足类型要求
let kp1: KeyPair<number, string> = { key: 123, value: 'str' }
let kp2: KeyPair<string, number> = { key: 'test', value: 123 }

// 描述函数的泛型接口
interface IPlus<T> {
  // 函数应具有两个形参,和一个返回值,它们的类型相同
  (a: T, b: T): T
}

function plus(a: number, b: number): number {
  return a + b
}
function concat(a: string, b: string): string {
  return a + b
}

参数类型不兼容:

image