基础语法


# 基础语法

# 1.数据类型

# 1.1 布尔类型(boolean)

let meried: boolean = false
1

# 1.2 数字类型(number)

let age: number = 40
1

# 1.3 字符串类型(string)

let nameUser: string = 'laoqin'
1

# 1.4 数组类型(array)

let arr1: Array<number> = [1, 2, 3]
let arr2: string[] = ['hello', 'wold']
1
2

# 1.5 元组类型

在 TypeScript 的基础类型中,元组( Tuple )表示一个已知 数量类型 的数组

let example: [string, number] = ['hello', 12]
example[0].length
example[1].toFixed(2)
1
2
3
元组 数组
每一项都可以是不同的类型 每一项都是同一种类型
有预定义的长度 没有长度限制
用于表示一个固定的解构 用于表示一个列表
const animal: [string, number, boolean] = ['猫', 1, true]
1

# 1.6 枚举类型

  • 事先考虑某一个变量的所有可能的值, 尽量用字眼语言中的单词表示它的每一个值
  • 比如性别, 月份, 星期, 颜色, 单位, 学历

# 👉 1.6-1 普通枚举

普通枚举

// 1. 可以直接赋值的写法
enum Gender {
    man = "男",
    woman = "女"
}
console.log(Gender.man)
// 第二种通过索引拿到值
enum Gender2 {
    "男",
    "女"
}
console.log(Gender2["0"])
1
2
3
4
5
6
7
8
9
10
11
12

# 👉 1.6-2 常数枚举

常数枚举

  • 常数枚举与普通枚举的区别是, 它会在编译阶段被删除, 并且不能包含计算成员
  • 假如包含了计算成员, 则会在编译阶段报错
const enum Colors{
    Red,
    Yellow
}
let color = [Colors.Red]
1
2
3
4
5

# 1.7 任意类型(any)

何时使用?

  • any 就是可以赋值给任意数据类型
  • 第三方库没有提供类型文件时可以使用 any
  • 类型转换遇到困难时
  • 数据结构太复杂难以定义
let dom: any = document.getElementById('root')
1

# 1.8 非空断言

let dom: HTMLElement | null = document.getElementById("root")
dom!.style.color = 'red'
1
2

# 1.9 null 和 undefind

  • null 和 undefind 是其它类型的子类型时, 可以赋值给其它类型,如数字类型,此时, 赋值后的类型会变成 null 和 undefind
  • 配置文件中strictNullChecks 参数用于新的严格空检查模式,在严格空检查模式下
  • null 和 undefined 值都不属于任何一个类型,它们只能赋值给自己这种类型或者 any
let x: number
x = 1
x = undefined
x = null

let y: number | null | undefined
y = 1
y = undefined
y = null
1
2
3
4
5
6
7
8
9

# 1.10 void 类型

void 类型

  • void 表示没有任何类型
  • 当一个函数没有返回值时, ts 会认为它的返回值是 void 类型
function greeting(name: string): void {
  console.log('hello', name)
  //当我们声明一个变量类型是 void 的时候,
  //它的非严格模式(strictNullChecks:false)下仅可以被赋值为 null 和 undefined
  //严格模式(strictNullChecks:true)下只能返回undefined
  //return null;
  //return undefined;
}
1
2
3
4
5
6
7
8

# 1.11 never 类型(开发中基本不会用到)

never是其它类型(null undefined)的子类型,代表不会出现的值

  • 作为不会返回( return )的函数的返回值类型
// 返回never的函数 必须存在 无法达到( unreachable ) 的终点
function error(message: string): never {
  throw new Error(message)
}
let result1 = error('hello')
// 由类型推论得到返回值为 never
function fail() {
  return error('Something failed')
}
let result = fail()

// 返回never的函数 必须存在 无法达到( unreachable ) 的终点
function infiniteLoop(): never {
  while (true) {}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 1.12 never 和 void 的区别

  • void 可以被赋值为 null 和 undefined 的类型。 never 则是一个不包含值的类型。
  • 拥有 void 返回值类型的函数能正常运行。拥有 never 返回值类型的函数无法正常返回,无法终止,或会抛出异常。

# 1.13 类型推论

  • 是指编程语言中能够自动推导出值的类型的能力,它是一些强静态类型语言中出现的特性
  • 定义时未赋值就会推论成 any 类型
  • 如果定义的时候就赋值就能利用到类型推论
let username2
username2 = 10
username2 = 'zhufeng'
username2 = null
1
2
3
4

# 1.14 包装对象(Wrapper Object)

  • JavaScript 的类型分为两种:原始数据类型(Primitive data types)和对象类型(Object types)。
  • 所有的原始数据类型都没有属性(property)
  • 原始数据类型(布尔值, 数值, 字符串, null, undefined, Symbol)
let name = 'zhufeng'
console.log(name.toUpperCase())
console.log(new String('zhufeng').toUpperCase())
1
2
3
  • 当调用基本数据类型方法的时候,JavaScript 会在原始数据类型和对象类型之间做一个迅速的强制性切换
let isOK: boolean = true // 编译通过
let isOK: boolean = Boolean(1) // 编译通过
let isOK: boolean = new Boolean(1) // 编译失败   期望的 isOK 是一个原始数据类型
1
2
3

# 1.15 类型断言

  • 类型断言可以将一个联合类型的变量,指定为一个更加具体的类型
  • 不能将联合类型断言为不存在的类型
let name: string | number;
console.log((name as string).length);
console.log((name as number).toFixed(2));
console.log((name as boolean));
1
2
3
4

# 1.16 字面量类型

  • 可以把字符串、数字、布尔值字面量组成一个联合类型
type ZType = 1 | 'One' | true
let t1: ZType = 1
let t2: ZType = 'One'
let t3: ZType = true
1
2
3
4

# 1.17 字符串字面量 vs 联合类型

  • 字符串字面量类型用来约束取值只能是某几个字符串中的一个, 联合类型(Union Types)表示取值可以为多种类型中的一种
  • 字符串字面量 限定了使用该字面量的地方仅接受特定的值,联合类型 对于值并没有限定,仅仅限定值的类型需要保持一致

# 2.函数

# 2.1 函数定义(声明)

  • 指定参数的类型 和 返回值的类型
function mess(name: string): void {
  console.log(name)
}
mess('laowang')
1
2
3
4

# 2.2 函数表达式

  • 使用 type 定义类型
type funType = (x: string, y: string) => string
let s: funType = function(x, y) {
  return x + y
}
console.log(s('j', 'l'))
1
2
3
4
5

# 2.3 没有返回值

let nomess = function(name: string): void {
  console.log(name)
  return undefined
}
nomess('hello')
1
2
3
4
5

# 2.4 可选参数

  • 在 TS 中函数的形参和实参必须一样,不一样就要配置可选参数,而且必须是最后一个参数
function print(name: string, age?: number): void {
  console.log(name, age)
}
print('qin')
1
2
3
4

# 2.5 默认参数

  • 给参数一个默认值
function ajax(url: string, method: string = 'GET') {
  console.log(url, method)
}
ajax('/users')
1
2
3
4

# 2.6 剩余参数

  • 函数中余下的参数可以进行一次性取值
// 示例代码1
function sum(...numbers: number[]) {
  return numbers.reduce((val, item) => (val += item), 0)
}
console.log(sum(1, 2, 3))
// 示例代码2
function add(a: number, ...b: number[]) {
  console.log(a)
  console.log(b)
}

add(1, 2, 3, 4, 5, 8, 7)
1
2
3
4
5
6
7
8
9
10
11
12

# 2.7 函数重载

  • 在 TypeScript 中,表现为给同一个函数提供多个函数类型定义

如下,该函数只能传入 string 或者 number 类型的参数

function attr(val: string): void
function attr(val: number): void
function attr(val: any): void {
  let a, b
  val === "string" ? (a = val) : (b = val)
  console.log(a, b)
}
attr(15)
1
2
3
4
5
6
7
8

# 3.类

# 3.1 类的定义

  • "strictPropertyInitialization": true / 启用类属性初始化的严格检查/
  • name!:string 定义一个简单类
class Person {
  name: string
  getName(): void {
    console.log(this.name)
  }
}
let p = new Person()
p.name = '章梓'
p.getName() // 章梓
1
2
3
4
5
6
7
8
9

# 3.2 类的存取器

  • 在 TypeScript 中,我们可以通过存取器来改变一个类中属性的读取和赋值行为

  • 构造函数

    • 主要用于初始化类的成员变量属性
    • 类的对象创建时自动调用执行
    • 没有返回值

下面代码中的 get 和 set 后边的名字定义成什么无所谓

class Person {
  stuName: string
  constructor(name: string) {
    this.stuName = name
  }
  get name() {
    return this.stuName
  }
  set name(value) {
    this.stuName = value
  }
}
let p = new Person('章子')
console.log(p) // Person { stuName: '章子' }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 3.3 参数修饰符(参数属性)

  • constructor 构造函数 public 修饰的参数, 可以省略赋值
  • public, protected, readonly, private ,即可以修饰参数,也可以修饰自己的属性
class User {
    constructor(public myname: string) {}
    get name() {
        return this.myname;
    }
    set name(value) {
        this.myname = value;
    }
}

let user = new User('核子');
console.log(user.name);
user.name = '原子';
console.log(user.name);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 3.4 readonly

  • readonly 修饰的变量只能在构造函数中初始化
  • 在 TypeScript 中,const 是常量标志符,其值不能被重新分配
  • TypeScript 的类型系统同样也允许将 interface、type、 class 上的属性标识为 readonly
  • readonly 实际上只是在编译阶段进行代码检查。
  • 而 const 则会在运行时检查(在支持 const 语法的 JavaScript 运行时环境中)
class Animal {
    public readonly name: string
    constructor(name:string) {
        this.name = name;
    }
    changeName(name:string){
        // 这里会报错提示 该属性是 只读的
        this.name = name;
    }
}
1
2
3
4
5
6
7
8
9
10

# 3.5 类的继承

  • 子类继承父类后子类的实例就拥有了父类中的属性和方法,可以增强代码的可复用性
  • 将子类公用的方法抽象出来放在父类中,自己的特殊逻辑放在子类中重写父类的逻辑
  • super 可以调用父类上的方法和属性
// 定义一个描述人的类 有姓名, 年龄
class Person {
  constructor(public name: string, public age: number) {}
  getName(): string {
    return this.name
  }
  setName(val): void {
    this.name = val
  }
}
// 定义一个学生类继承 Person 类
class Student extends Person {
  No: number // 学号
  constructor(name: string, age: number, No: number) {
    super(name, age) // 继承父类的名字,年纪
    this.No = No // 自己的学号
  }
  // 子类自己的方法
  getNo(): number {
    return this.No
  }
}
const p = new Student("lisi", 15, 215)
console.log(p)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 3.6 类里面的修饰符

主要用来描述修饰自身的属性

  • public, protected, readonly, private 修饰自己的属性
  • public: 类里面 子类 其它任何地方外边都可以访问
  • protected: 类里面 子类 都可以访问,其它任何地方不能访问
  • readonly: 属性只能自己读取
  • private: 类里面可以访问, 子类和其它任何地方都不可以访问
class Father {
    public name: string;  //类里面 子类 其它任何地方外边都可以访问
    protected age: number; //类里面 子类 都可以访问,其它任何地方不能访问
    private money: number; //类里面可以访问, 子类和其它任何地方都不可以访问
    constructor(name:string,age:number,money:number) {//构造函数
        this.name=name;
        this.age=age;
        this.money=money;
    }
    getName():string {
        return this.name;
    }
    setName(name:string): void{
        this.name=name;
    }
}
class Child extends Father{
    constructor(name:string,age:number,money:number) {
        super(name,age,money);
    }
    desc() {
        console.log(`${this.name} ${this.age} ${this.money}`);
    }
}

let child = new Child('河马',10, 1000);
console.log(child.name);
console.log(child.age);
console.log(child.money);
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

# 3.7 类的静态(属性和方法)

  • (属性和方法)只能使用类名 调用
class Father {
    static className = 'Father';
    static getClassName() {
        return Father.className;
    }
    constructor(public name:string) {}
}
console.log(Father.className);
console.log(Father.getClassName());
1
2
3
4
5
6
7
8
9

# 3.8 装饰器

  • 装饰器是一种特殊类型的声明,它能够被附加到类声明、方法、属性或参数上,可以修改类的行为
  • 常见的装饰器: 类装饰器属性装饰器方法装饰器参数装饰器
  • 装饰器的写法: 普通装饰器装饰器工厂
  • 装饰器的语法: @函数名 || @函数名(传参)

# 👉3.8.1 类装饰器


  • 类装饰器在类声明之前声明, 用来监视, 修改和替换类定义

  • 如果你再装饰器函数中,扩展了当前类的属性和方法

  • 在调用时, 就必须要提供一个和类名一样的接口名,来约束你的扩展

    第一个参数是target(指向下边定义的类)

  • 下边的这个方法,提供了一个类装饰器person函数,扩展了name属性eat 方法,
  • 提供了一个和类名字一样的接口名字(必须一样,否则报错)
namespace q {
  // 定义一个和类名字一样的接口名字, 来约束装饰器中的扩展
  interface Person {
    name: string
    eat: () => void
  }
    //   装饰器对应的函数
  function person(target: any) {
    target.prototype.name = "hello"
    target.prototype.eat = function() {
      console.log("eat")
    }
  }
  // 类装饰器在类声明之前声明,用来监视、修改或替换类定义
  @person
  class Person {
    constructor() {}
  }
  let p: Person = new Person()
//  如果不提供装饰器, 以下的调用就会报错
  console.log(p.name) // hello
  p.eat() // eat
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 👉👉 1.1 类装饰器传参


  • 类装饰器要传参, 装饰器函数必须返回一个高阶函数
namespace q {
  type eatFun = () => void
  interface Person {
    name: string
    eat: eatFun
  }
  // 如果装饰器传参数, 必须使用高阶函数
  function person(name: string) {
    return (target: any) => {
      target.prototype.name = name
      target.prototype.eat = function() {
        console.log("eat")
      }
    }
  }
  // 类装饰器在类声明之前声明,用来监视、修改或替换类定义
  @person("老王")
  class Person {
    constructor() {}
  }
  let p: Person = new Person()
  console.log(p.name) // 老王
  p.eat()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 👉👉 1.2 类装饰器返回类


    1. 类装饰器中 装饰器函数也可以返回一个新的匿名类覆盖原来的类
namespace q {
  interface Person {
    name: string
    eat: () => void
  }
  // 也可以返回一个新类
  function person(target: any) {
    return class {
      name: string = "我是"
      eat() {
        console.log("eat苹果")
      }
    }
  }
  // 类装饰器在类声明之前声明,用来监视、修改或替换类定义
  @person
  class Person {
    constructor() {}
  }
  let p: Person = new Person()
  console.log(p.name) // 我是
  p.eat() //eat苹果
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    1. 返回的新类中必须要有原来类中的属性名 和 方法名, 等于就是重写原有类的方法和属性,否则编译报错

下边返回的新类,重写了类中的属性和方法,注释掉@person会报错

interface People {
  name: string
  eat: () => void
}
function person(target: any) {
  return class {
    name: string = 'hello'
    name1: string = '重写的'
    eat() {
      console.log(this.name)
    }
    eat1() {
      console.log('eat重写原有类的eat1方法')
    }
  }
}
@person
class People {
  name1: string = 'people'
  constructor() {}
  eat1() {
    console.log('eat')
  }
}

let p = new People()
console.log(p.name)
p.eat()
p.eat1()
console.log(p.name1)
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

# 👉3.8.2 属性装饰器


属性装饰器

  • 属性装饰器表达式会在运行时当作函数被调用,传入下列 2 个参数(target, prototypeName)
  • 属性装饰器用来装饰属性
    • 第一个参数对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
    • 第二个参数是属性的名称
  • 对于要修改的属性, 必须使用 Object.defineProperty(),进行重写对象的 get 和 set 方法,不允许直接返回修改的值,
  • 否则会报错:
    • 属性修饰器函数的返回类型必须为 "void" 或 "any"。 作为表达式调用时,无法解析属性修饰器的签名
    • The return type of a property decorator function must be either 'void' or 'any'.
  • 属性装饰器 也是一个函数, 写在属性上边 格式为 @函数名
// 属性装饰器
namespace a {
  function upcaseName(target: any, protoKey: string) {
    let value = target[protoKey]
    return Object.defineProperty(target, protoKey, {
      get() {
        return value
      },
      set(newval: string) {
        value = newval.toUpperCase()
      }
    })
  }
  class Person {
    @upcaseName // 此装饰器作用让 name 属性大写
    name: string = "爱罗"
    public static age = 10 // 静态属性,通过类调用
    constructor() {}
  }
  let p = new Person()
  p.name = "hello"
  console.log(p.name)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 👉3.8.3 方法装饰器

  • 方法装饰器:也是一个函数, 写在方法的上边,格式为 @函数名, 它有 3 个参数

方法装饰器

  • 方法装饰器用来装饰方法
    • 第一个参数对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
    • 第二个参数是方法的名称
    • 第三个参数是方法描述符(类型是PropertyDescriptor)
  • 用法:
      1. 如果原来的方法是有返回值的, 方法装饰器可以通过第三个参数的value方法,可以拿到原类中的方法的返回值
      1. 然后重写第三个参数的value方法, return 你的返回值, 就可以重写原先类中的方法
    1. 如果是没有返回值, 就是使用第三个参数的value重写一下这个方法
// 类的装饰器方法
function changeName(target: any, methodName: string, desc: PropertyDescriptor) {
  let oldValue = desc.value() // 这个可以拿到值 `李四`
  //   return oldValue + ',hello world' // 不能直接在这里return 否则,装饰器报错
  desc.value = function() {
    return oldValue + ',hello world'
  }
}
class People1 {
  constructor() {}
  @changeName
  sayName() {}
}
let p1 = new People1()
console.log(p1.sayName()) // undefined,hello world
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
namespace a {
  /**
   * 方法装饰器函数
   * @param target 被装饰的类
   * @param methodName 被装饰的类中的方法的名字
   * @param descriptor 被装饰的类中的方法描述符
   */
  function toNumber(
    target: any,
    methodName: string,
    descriptor: PropertyDescriptor
  ) {
    let oldMethod = descriptor.value
    descriptor.value = function(...args: any[]) {
      args = args.map(item => +item)
      return oldMethod.apply(this, args)
    }
  }
  class Person {
    @toNumber // 装饰器的作用: 不管你传入什么字符串,都让它变成数字
    sum(...args: any[]) {
      return args.reduce((accu: number, item: number) => accu + item, 0)
    }
  }
  let p = new Person()
  console.log(p.sum("1", "2", "2")) //5
}
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

# 👉3.8.4 参数装饰器

  • 会在运行时当作函数被调用,可以使用参数装饰器为类的原型增加一些元数据
  • 第 1 个参数对于静态成员是类的构造函数,对于实例成员是类的原型对象
  • 第 2 个参数的是方法的名字
  • 第 3 个参数是该参数在函数的参数列表中的索引
  • 如果是新定义的属性, 必须提供一个接口来定义数据类型
namespace a {
  interface Person {
    age: number
  }
  function addAge(target: any, paramsName: string, index: number) {
    console.log(target) // Person{}
    console.log(paramsName) // login
    console.log(index) // 1
    target.age = 20
  }
  class Person {
    //@addAge 添加一个新的 age 参数
    login(userName: string, @addAge password: string) {
      console.log(this.age) // 20
    }
  }
  let p = new Person()
  p.login("laoli", "123456")
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 👉3.8.5 装饰器执行顺序

执行顺序

先属性装饰器 👉 方法中的参数装饰器 👉 方法装饰器 👉 类的装饰器


  • 有多个参数装饰器时:从最后一个参数依次向前执行
  • 方法和方法参数中参数装饰器先执行。
  • 类装饰器总是最后执行
  • 方法和属性装饰器,谁在前面谁先执行。因为参数属于方法一部分,所以参数会一直紧紧挨着方法执行
namespace e {
    function Class1Decorator() {
        return function (target: any) {
            console.log("类1装饰器");
        }
    }
    function Class2Decorator() {
        return function (target: any) {
            console.log("类2装饰器");
        }
    }
    function MethodDecorator() {
        return function (target: any, methodName: string, descriptor: PropertyDescriptor) {
            console.log("方法装饰器");
        }
    }
    function Param1Decorator() {
        return function (target: any, methodName: string, paramIndex: number) {
            console.log("参数1装饰器");
        }
    }
    function Param2Decorator() {
        return function (target: any, methodName: string, paramIndex: number) {
            console.log("参数2装饰器");
        }
    }
    function PropertyDecorator(name: string) {
        return function (target: any, propertyName: string) {
            console.log(name + "属性装饰器");
        }
    }

    @Class1Decorator()
    @Class2Decorator()
    class Person {
        @PropertyDecorator('name')
        name: string = 'zhufeng';
        @PropertyDecorator('age')
        age: number = 10;
        @MethodDecorator()
        greet(@Param1Decorator() p1: string, @Param2Decorator() p2: string) { }
    }
}
/**
name属性装饰器
age属性装饰器
参数2装饰器
参数1装饰器
方法装饰器
类2装饰器
类1装饰器
 */
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

# 3.9 抽象类

抽象类定义

  • 描述一种抽象的概念, 无法被实例化, 只能被继承
  • 无法创建抽象类的实例
  • 抽象方法不能再抽象类中实现, 只能在抽象类的具体子类中实现, 而且是必须实现
abstract class Animal {
  name!: string
  abstract speak(): void
}

class Cat extends Animal {
  name: string = "小花"
  speak() {
    console.log(this.name + "....瞄....")
  }
}
// 报错, 无法创建实例
// let a = new Animal()

// 抽象类必须被子类实现, 子类要重写实现继承的抽象类的方法
let c = new Cat()
console.log(c.name) //小花
c.speak() // 小花....瞄....
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
访问控制修饰符 private, protected, public 可以修饰以下属性
只读属性 readonly
静态属性 static
抽象类、抽象方法 abstract

# 3.10 抽象方法

说明

  • 抽象类和方法不包含具体实现,必须在子类中实现
  • 抽象方法只能出现在抽象类中
  • 子类可以对抽象类进行不同的实现
// 抽象类
abstract class Animal{
    abstract speak():void;
}
// 子类实现抽象类的方法
class Dog extends  Animal{
    speak(){
        console.log('小狗汪汪汪');
    }
}
class Cat extends  Animal{
    speak(){
        console.log('小猫喵喵喵');
    }
}
let dog=new Dog();
let cat=new Cat();
dog.speak(); // 小狗汪汪汪
cat.speak(); //小猫喵喵喵
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 3.11 抽象类 VS 接口

对比

  • 不同类之间公有的属性或方法,可以抽象成一个接口(Interfaces)
  • 而抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现
  • 抽象类本质是一个无法被实例化的类,其中能够实现方法和初始化属性,而接口仅能够用于描述,既不提供方法的实现,也不为属性进行初始化
  • 一个类可以继承一个类或抽象类,但可以实现(implements)多个接口
  • 抽象类也可以实现接口

抽象类 和 接口 一块使用


 



 


















// 定义一个接口
interface Flying {
    fly(): void;
}
// 定义一个抽象类
abstract class Animal{
    name:string;
    constructor(name:string){
        this.name = name;
    }
    abstract speak():void;
}
class Bird extends Animal implements Flying{
    speak(){
        console.log('急急急');
    }
    fly(){
        console.log('我会飞');
    }
}
let bird = new Bird('啥鸟');
bird.speak(); // 急急急
bird.fly(); // 我会飞
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 3.12 重写(override)和重载(overload)

  • 重写是指子类重写继承自父类中的方法
  • 重载是指为同一个函数提供多个类型定义

重写

class People {
  speak(word: string): string {
    return 'People叫:' + word
  }
}
class Student extends People {
  speak(msg: string): string {
    return 'Student叫' + msg
  }
}
let s = new Student()
console.log(s.speak('hello'))
1
2
3
4
5
6
7
8
9
10
11
12

重载

function fn(a: number): number
function fn(a: string): string
function fn(a: any): any {
  return typeof a === "string" ? a.toUpperCase() : a * 10
}
1
2
3
4
5

# 3.13 继承 VS 多态

  • 继承(Inheritance)子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
  • 多态(Polymorphism)由继承而产生了相关的不同的类,对同一个方法可以有不同的行为
class Animal {
  speak(word: string): string {
    return 'Animal: ' + word
  }
}
class Cat extends Animal {
  speak(word: string): string {
    return 'Cat:' + word
  }
}
class Dog extends Animal {
  speak(word: string): string {
    return 'Dog:' + word
  }
}
let cat = new Cat()
console.log(cat.speak('我是猫'))
let dog = new Dog()
console.log(dog.speak('我是狗'))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 4. 接口

接口定义

  • 接口一方面可以在面向对象编程中表示为行为的抽象,另外可以用来描述对象的形状
  • 接口就是把一些类中共有的属性和方法抽象出来,可以用来约束实现此接口的类
  • 一个类可以继承另一个类并实现多个接口
  • 接口像插件一样是用来增强类的,而抽象类是具体类的抽象概念
  • 一个类可以实现多个接口,一个接口也可以被多个类实现,但一个类的可以有多个子类,但只能有一个父类
  • 语法: interface中可以用分号或者逗号分割每一项,也可以什么都不加

# 4.1 描述对象

接口可以用来描述对象的形状,少属性或者多属性都会报错

函数多是函数表达式的语法

interface Speakable {
  speak(): void;
  name?: string; //?表示可选属性
}
let speakman: Speakable = {
  speak() {}, //少属性会报错
  name,
  age //多属性也会报错
}
1
2
3
4
5
6
7
8
9

# 4.2 描述行为的抽象

接口实现类

interface Speak {
  speak(): void;
}
interface Eat {
  eat(): void;
}
// 一个类可以实现多个接口
class People implements Speak, Eat {
  speak() {
    console.log("people说")
  },
  eat() {
    console.log("people吃")
  }
}
class DuMa implements Speakable{
    speak(){
        console.log('DuMa说话');
    }
    eat(){}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 4.3 对象中的任意的属性

  • 无法预先知道有哪些新的属性的时候,可以使用 [propName:string]:any,propName 名字是任意的
interface Person {
  readonly id: number;
  name: string;
  [propName: string]: any;
}
// 定义的时候,可以传除要求之外的 其它任意属性
let p1 = {
  id:1,
  name:'zhufeng',
  age:10, // 其它的属性
  age2:20, // 其它的属性
}
1
2
3
4
5
6
7
8
9
10
11
12

# 4.4 接口的继承

  • 一个接口可以继承自另外一个接口
interface Speak {
  speak(): void;
}
interface SpeakChinese extends Speak {
  speakChinese(): void;
}
class Person implements SpeakChinese {
  speak() {
    console.log('Person')
  }
  speakChinese() {
    console.log('speakChinese')
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 4.5 接口中 readonly

  • 用 readonly 定义只读属性可以避免由于多人协作或者项目较为复杂等因素造成对象的值被重写
interface Person{
  readonly id:number;
  name:string
}
let tom:Person = {
  id :1,
  name:'zhufeng'
}
tom.id = 1;
1
2
3
4
5
6
7
8
9

# 4.6 接口描述函数类型

  • 含义: 对方法传入的参数和返回值进行约束
interface discount {
  (price: number): number;
}
// 函数表达式
let cost: discount = function(price: number): number {
  return price * 0.8
}
1
2
3
4
5
6
7

# 4.7 可索引接口

  • 对数组和对象进行约束
  • userInterface 表示 index 的类型是 number,那么值的类型必须是 string
  • UserInterface2 表示:index 的类型是 string,那么值的类型必须是 string
// 约束数组
interface UserInterface {
  [index: number]: string;
}
let arr: UserInterface = ['zfpx1', 'zfpx2']
console.log(arr)
// 约束对象
interface UserInterface2 {
  [index: string]: string;
}
let obj: UserInterface2 = { name: 'zhufeng' }
1
2
3
4
5
6
7
8
9
10
11

# 4.8 类接口

  • 对类的约束
interface Speakable {
    name: string;
    speak(words: string): void
}
// 实现接口
class Dog implements Speakable {
    name!: string;
    speak(words:string) {
        console.log(words);
    }
}
let dog = new Dog();
dog.speak('汪汪汪');
1
2
3
4
5
6
7
8
9
10
11
12
13

# 4.9 构造函数的类型

  • 在 TypeScript 中,我们可以用 interface 来描述类
  • 同时也可以使用 interface 里特殊的 new()关键字来描述类的构造函数类型
class Animal {
  constructor(public name: string) {}
}
interface WithNameClass {
  new (name: string): Animal
}
function createAnimal(clazz: WithNameClass, name: string) {
  return new clazz(name)
}
let a = createAnimal(Animal, "猫")
console.log(a.name)
1
2
3
4
5
6
7
8
9
10
11

# 4.10 接口描述函数参数

  // 描述函数的参数
  interface pramsType {
    name: string
    age: number
  }
  //  将 pramsType 约束参数类型
  function add2(params: pramsType): void {
    console.log(params.name + params.age)
  }
  console.log(add2({ name: 'lisi', age: 12 }))
1
2
3
4
5
6
7
8
9
10

# 5. 泛型

含义

  • 泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性
  • 泛型 T 作用域只限于函数内部使用

# 5.1 泛型函数

  • 首先,我们来实现一个函数 createArray,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值

数据类型提前指定

function createArray(length: number, value: any): Array<any> {
  let result: any = []
  for (let i = 0; i < length; i++) {
    result[i] = value
  }
  return result
}
let result = createArray(3, 'x')
console.log(result)
1
2
3
4
5
6
7
8
9

使用泛型<具体传入什么类型,调用的时候决定>

function createArray<T>(length: number, value: T): Array<T> {
  let result: T[] = []
  for (let i = 0; i < length; i++) {
    result[i] = value
  }
  return result
}
let result = createArray2 < string > (3, 'x')
console.log(result)
1
2
3
4
5
6
7
8
9

不能提前确定具体的属性和值,不能先进行运算,因为这时候,还不知道你传入什么值

namespace E {
  interface Fun {
    name: string
  }

  let add = function<T>(data: T): void {
    // console.log(data.name) 会报错,因为这时候它不知道你传入什么类型
    for (let i in data) {
      console.log(data[i])
    }
  }
  add<Fun>({ name: 'lisi' })
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 5.2 类数组

  • 类数组(Array-like Object)不是数组类型,比如 arguments 使用了类型断言
function sum() {
    let args: IArguments = arguments;
    for (let i = 0; i < args.length; i++) {
        console.log(args[i]);
    }
}
sum(1, 2, 3);

let root = document.getElementById('root');
let children: HTMLCollection = (root as HTMLElement).children;
children.length;
let nodeList: NodeList = (root as HTMLElement).childNodes;
nodeList.length;
1
2
3
4
5
6
7
8
9
10
11
12
13

# 5.3 泛型类

// 1,外部实现属性和方法
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; };

// 2. 内部实现属性和方法(把元素放进数组, 求数组的最大值)

class MyArray<T> {
  private list: T[] = []
  add(value: T) {
    this.list.push(value)
  }
  getMax(): T {
    let result = this.list[0]
    for (let i = 0; i < this.list.length; i++) {
      if (this.list[i] > result) {
        result = this.list[i]
      }
    }
    return result
  }
}
let arr = new MyArray<number>()
arr.add(1)
arr.add(3)
let ret = arr.getMax()
console.log(ret)
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

# 5.4 泛型接口

  • 泛型接口可以用来约束函数
interface Calculate {
  <T>(a: T, b: T): T;
}
let add: Calculate = function<T>(a: T, b: T) {
  return a
}
add < number > (1, 2)
1
2
3
4
5
6
7

# 5.5 多个类型参数

  • 定义泛型的时候,可以一次定义多个类型参数
function swap<T, U>(tuple: [T, U]): [U, T] {
  return [tuple[1], tuple[0]]
}

swap([7, 'seven'])
1
2
3
4
5

# 5.6 默认泛型类型

  • 默认传入的是 string 类型
function createArray<T = string>(length: number, value: T): Array<T> {
  let result: T[] = []
  for (let i = 0; i < length; i++) {
    result[i] = value
  }
  return result
}
1
2
3
4
5
6
7

# 5.7 泛型约束

  • 在函数中使用泛型的时候,由于预先并不知道泛型的类型,所以不能随意访问相应类型的属性或方法。

👇

function logger<T>(val: T) {
  console.log(val.length) //直接访问会报错
}
1
2
3

👇 让泛型继承一个接口

// 定义一个接口
interface LengthWise {
  length: number
}
function logger2<T extends LengthWise>(val: T) {
  console.log(val.length) // 必须有一个length 方法
}
logger2<string>("海南")

1
2
3
4
5
6
7
8
9

# 5.8 泛型接口

  • 定义接口的时候也可以指定泛型
interface Cart<T> {
  list: T[];
}

let cart: Cart<{ name: string, price: number }> = {
  list: [{ name: '李四', price: 10 }]
}
console.log(cart.list) // [{ name: "李四", price: 10 }]
1
2
3
4
5
6
7
8

# 5.9 泛型类型别名

  • 别名可以表达更复杂的类型
type Cart<T> = { list: T[] } | T[]
let c1: Cart<string> = { list: ['1'] }
let c2: Cart<number> = [1]
1
2
3

# 5.10 泛型接口 vs 泛型类型别名

区别

  • 接口创建了一个新的名字,它可以在其他任意地方被调用。而类型别名并不创建新的名字,例如报错信息就不会使用别名
  • 类型别名不能被 extends 和 implements,这时我们应该尽量使用接口代替类型别名
  • 当我们需要使用联合类型或者元组类型的时候,类型别名会更合适

# 6. 结构类型的兼容

# 6.1 接口的兼容性

  • 👉 如果传入的变量和声明的类型不匹配,TS 就会进行兼容性检查
  • 接口类型中声明的属性变量在上一个接口类型中都存在就是兼容的

那啥意思呢, 看下边的代码

下面代码定义两个接口, Persion接口比 Animal 接口多了一个gender

// 定义一个Animal 接口
interface Animal {
  name: string
  age: number
}
// 定义一个Person 接口
interface Person {
  name: string
  age: number
  gender: string
}
1
2
3
4
5
6
7
8
9
10
11

定义一个函数,让参数的 数据类型要求是 Animal 接口

// 定义一个函数
let fn = function(p: Animal) {
  console.log(p.name) // lisi
}
fn({ name: 'lisi', age: 15 })
1
2
3
4
5

下边先定义一个参数,要求它匹配定义的 Person 接口

let a: Person = {
  name: '老王',
  age: 15,
  gender: 'man'
}
let fn2 = function(p: Animal) {
  console.log(p.name) // 老王
}
fn2(a)
1
2
3
4
5
6
7
8
9

总结

上边函数表达式需要的数据类型, 只要传入的参数包含有需要的这两个属性, 允许你这个参数里多属性,但不允许你比我定义的少

简单理解: 我想要的你能给我, 而且在满足我想要的条件后,还可以附加多给我一些其它的东西,咱俩就继续, 否则,咱俩就拜拜


# 6.2 基本类型兼容性

// 定一个一个 b 变量,类型是 string 或者 number的
let b: string | number
// 定义一个 a 变量, 类型是string 默认值是Hello
let a: string = 'Hello'
// 把a的值,赋值给num(只要你赋值的值满足我的条件之一就可以)
b = a
console.log(b.toUpperCase()) //HELLO
1
2
3
4
5
6
7

# 6.3 类的兼容性

  • 类与对象字面量和接口差不多
  • 不同的是:
    • 类有静态部分和实例部分的类型。
    • 比较两个类型的对象时,只有实例的成员会被比较。
    • 静态成员和构造函数不在比较的范围内。



# 6.4 函数兼容性

  • 比较函数的时候是要先比较函数的参数,再比较函数的返回值

# 👉6.4.1 比较参数









 





 





 



 



type sumFunc = (a: number, b: number) => number

let sum: sumFunc
function f1(a: number, b: number): number {
  return a + b
}
sum = f1

//可以省略一个参数
function f2(a: number): number {
  return a
}
sum = f2

//可以省略二个参数
function f3(): number {
  return 0
}
sum = f3

//多一个参数可不行
function f4(a: number, b: number, c: number) {
  return a + b + c
}
// 报错
// 不能将类型“(a: number, b: number, c: number) => number”分配给类型“sumFunc”
sum = f4
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

# 👉6.4.2 比较返回值

  • 返回值和定义的结构一样可以
  • 返回值和比定义的结构多属性可以
  • 返回值和比定义的结构少属性不可以



 




 





 






type GetPerson = () => { name: string, age: number }
let getPerson: GetPerson
//返回值一样可以
function g1() {
  return { name: 'zhufeng', age: 10 }
}
getPerson = g1
//返回值多一个属性也可以
function g2() {
  return { name: 'zhufeng', age: 10, gender: 'male' }
}
getPerson = g2

//返回值少一个属性可不行
function g3() {
  return { name: 'zhufeng' }
}
// 这里赋值会报错 `'age' is declared here`
getPerson = g3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 6.5 函数参数协变

// 定义一个数据类型
type LogFunc = (a: number | string) => void

// 定义一个变量log,它的类型是LogFunc
let log: LogFunc

// 定义一个函数
function log1(a: number | string | boolean) {
  console.log(a)
}
//赋值, 如果log1 函数需要的数据类型 满足 log 变量就是可以的
log = log1
log(1)
1
2
3
4
5
6
7
8
9
10
11
12
13

# 6.6 泛型的兼容性

  • 泛型在判断兼容性的时候会先判断具体的类型,然后再进行兼容性判断
//1.接口内容为空没用到泛型的时候是可以的
interface Empty<T> {}
let x!: Empty<string>
let y!: Empty<number>
x = y
//2.接口内容不为空的时候不可以
interface NotEmpty<T> {
  data: T
}
let x1!: NotEmpty<string>
let y1!: NotEmpty<number>
//不能将类型“NotEmpty<number>”分配给类型“NotEmpty<string>”
//不能将类型“number”分配给类型“string”
x1 = y1

// 下边这样才可以
let x2: NotEmpty<string> = { data: "10" }
console.log(x2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 6.7 枚举的兼容性

  • 枚举类型与数字类型兼容,并且数字类型与枚举类型兼容
  • 不同枚举类型之间是不兼容的








 
 






//数字可以赋给枚举
enum Colors {
  Red,
  Yellow
}
let c: Colors
c = Colors.Red
c = 1
// 报错 不能将类型“"1"”分配给类型“Colors”
c = "1"

//枚举值可以赋给数字
let n: number
n = 1
n = Colors.Red
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 7. 类型保护

  • 类型保护就是一些表达式,他们在编译的时候就能通过类型信息确保某个作用域内变量的类型
  • 类型保护就是能够通过关键字判断出分支中的类型

# 7.1 typeof 类型保护

// 通过typeof 判断不同的数据类型的情况下, 返回不同的结果, 同原生 js 判断一样
function double(input: string | number | boolean) {
  if (typeof input === 'string') {
    return input + input
  } else {
    if (typeof input === 'number') {
      return input * 2
    } else {
      return !input
    }
  }
}
double(10) //传入数字
double('10') //传入字符串
double(true) //传入布尔值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 7.2 instanceof 类型保护

  • instanceof 判断谁是谁的实例 返回值 boolean
class Animal {
  name: string = 'Animal'
}
class Bird extends Animal {
  swing: number = 2
}
function getName(animal: Animal) {
  // 判断如果 animal 参数是  Bird 类的实例
  if (animal instanceof Bird) {
    console.log(animal.swing)
  } else {
    console.log(animal.name)
  }
}
let p = new Animal()

getName(p) // Animal
let p2 = new Bird()
getName(p2) // 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 7.3 null 类型保护

  • 类似于原生 js null 判断
  • 配置文件中(tsconfig.js)如果开启了 strictNullChecks 选项,那么对于可能为 null 的变量不能调用它上面的方法和属性
function getFirstLetter(s: string | null) {
  //第一种方式是加上null判断
  if (s == null) {
    return ""
  }
  //第二种处理是增加一个或的处理
  s = s || ""
  return s.charAt(0)
}
//它并不能处理一些复杂的判断,需要加非空断言操作符
function getFirstLetter2(s: string | null) {
  function log() {
    console.log(s!.trim())
  }
  s = s || ""
  log()
  return s.charAt(0)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 7.4 可辨识的联合类型

  • 就是利用联合类型中的共有字段进行类型保护的一种技巧
  • 相同字段的不同取值就是可辨识
interface WarningButton {
  class: "warning"
  text1: "修改"
}
interface DangerButton {
  class: "danger"
  text2: "删除"
}
type Button = WarningButton | DangerButton
function getButton(button: Button) {
  if (button.class == "warning") {
    console.log(button.text1)
  }
  if (button.class == "danger") {
    console.log(button.text2)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 7.5 in 操作符

  • in 运算符可以被用于参数类型的判断
interface Bird {
  swing: number;
}

interface Dog {
  leg: number;
}

function getNumber(x: Bird | Dog) {
  if ('swing' in x) {
    return x.swing
  }
  return x.leg
}
console.log(getNumber({ leg: 10 }))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 8. 类型变换

# 8.1 交叉类型

  • 交叉类型(Intersection Types)表示将多个类型合并为一个类型
  • 也就是可以合并两个类型, 如下的两个接口
// 定义接口 Bird
interface Bird {
  name: string
  fly(): void
}
// 定义接口 Person
interface Person {
  name: string
  talk(): void
}
// 定义一个数据类型结构组合 BirdPerson
type BirdPerson = Bird & Person
//  注意type 定义的东西不能打印啊
// console.log(BirdPerson)
// 使用这个类型
let p: BirdPerson = { name: "zhufeng", fly() {}, talk() {} }
p.fly
p.name
p.talk
//  报错 'fly' is declared here.
let p2: BirdPerson = { name: "zhufeng", talk() {} }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 8.2 typeof

  • 可以获取一个变量的类型

和 type 组合演示

//先定义类型,再定义变量
type People = {
  name: string,
  age: number,
  gender: string
}
let p1: People = {
  name: 'zhufeng',
  age: 10,
  gender: 'male'
}
1
2
3
4
5
6
7
8
9
10
11
let p1 = {
  name: 'zhufeng',
  age: 10,
  gender: 'male'
}
// 定义一个类型 ,是 p1 的数据类型
type People = typeof p1

function getName(p: People): string {
  return p.name
}
// 传入p1
getName(p1)
1
2
3
4
5
6
7
8
9
10
11
12
13

# 8.3 索引访问操作符

  • 可以通过[]获取一个类型的子类型
// 定义接口 Person
interface Person {
  name: string
  age: number
  job: {
    name: string
  }
  interests: { name: string; level: number }[]
}
// 定义一个变量, 类型是Person接口中的某个属性的子类型
let FrontEndJob: Person["job"] = {
  name: "前端工程师"
}
// 定义一个变量, 类型是Person接口中的某个属性的子类型
let interestLevel: Person["interests"][0]["level"] = 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 8.4 keyof 类型查询

  • 它是 索引类型查询操作符, keyof 会内部遍历变量接口定义的类型,然后取其中的某一个类型
interface Person {
  name: string
  age: number
  gender: "male" | "female"
}
//type PersonKey 是Person接口中定义的 'name'|'age'|'gender' 中的某一个;

type PersonKey = keyof Person

// 获取对象中某个key对应的值
function getValueByKey(p: Person, key: PersonKey) {
  return p[key]
}
// 调用
let val = getValueByKey(
  {
    name: "hello",
    age: 10,
    gender: "male"
  },
  "name"
)
console.log(val) // hello
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 8.5 in 操作符进行映射类型

  • 在定义的时候用 in 操作符去批量定义类型中的属性


















 









// 定义接口Person
interface Person {
  name: string
  age: number
  gender: "male" | "female"
}

//批量把一个接口中的属性都变成可选的
type PartPerson = {
  [Key in keyof Person]?: Person[Key]
}
// 使用时,赋值一个空对象
let p1: PartPerson = {}

//批量把一个接口中的属性都再重新描述一遍(演示用,开发中不会这么写的)
type PartPerson1 = {
  [Key in keyof Person]: Person[Key]
}
// 使用时,报错
let p2: PartPerson1 = {}

// //也可以使用泛型
type Part<T> = {
  [key in keyof T]?: T[key]
}
let p3: Part<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

# 8.6 内置类型工具

  • TS 中内置了一些工具类型来帮助我们更好地使用类型系统

Partial

Partial 可以将传入的属性由非可选变为可选

演示:

interface A {
  a1: string
  a2: number
  a3: boolean
}

// 属性必传
type aPartial1 = A
// Type '{}' is missing the following properties from type 'A': a1, a2, a3
const a1: aPartial1 = {}
console.log(a1) // 报错

// 使用Partial将属性变为可选
type aPartial = Partial<A>
const a: aPartial = {}
console.log(a) // 通过
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Required

Required 可以将传入的属性中的可选项变为必选项

演示:

interface Person {
  name: string
  age: number
  gender?: "male" | "female"
}

// 报错
let p: Required<Person> = {
  name: "zhufeng",
  age: 10
  //gender:'male'
}
// 通过
let p2: Required<Person> = {
  name: "zhufeng",
  age: 10,
  gender: "male"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

Readonly

Readonly 通过为传入的属性每一项都加上 readonly 修饰符来实现

演示:

interface Person {
  name: string
  age: number
  gender?: "male" | "female"
}
// 定义
let p: Readonly<Person> = {
  name: "美丽",
  age: 10,
  gender: "male"
}
// 修改age的值, 报错, 因为是只读的
p.age = 11
1
2
3
4
5
6
7
8
9
10
11
12
13

Pick

  • Pick 能够帮助我们从传入的属性中摘取某一项返回
interface Animal {
  name: string
  age: number
}

// 从 Animal 中的 name 属性
type AnimalSub = Pick<Animal, "name"> //{ name: string; }

// 使用AnimalSub类型,报错 因为只有 { name: string; }
// 不在类型“Pick<Animal, "name">”
let a: AnimalSub = {
  name: "zhufeng",
  age: 10
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 8.7 extends 条件类型

  • 定义泛型的时候能够添加进逻辑分支,以后泛型更加灵活
  • extends 就是继承自多个接口中一个, 也可以使用三元运算符
  • 语法: <T extends interfaceName>

定义一个条件类型




















 

interface Fish {
  name: string
}
interface Water {
  name: string
}
interface Bird {
  number: number
}
interface Sky {
  name: string
}
// T 是以上三个接口的任意一个

type Condition<T> = T extends Fish ? Water : Sky
let condition: Condition<Fish> = { name: "水" }

type Condition1<T> = T extends Fish ? Water : Bird
// 以下报错 Bird 不是 Fish
let condition1: Condition1<Bird> = { name: "水" }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

条件类型的分发

interface Fish {
  fish: string
}
interface Water {
  water: string
}

type Condition<T> = T extends Fish ? Fish : Water

let condition1: Condition<Fish | Water> = { water: "水" }

let condition2: Condition<Fish> = { fish: "天空" }

// 报错
let condition3: Condition<Water> = { fish: "天空" }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 8.8 内置条件类型

  • TS 在内置了一些常用的条件类型,可以在 lib.es5.d.ts 中查看:

# 👉8.8.1 Exclude(排除掉)

  • 语法: Exclude<多个类型, 排除掉哪个类型>
// 从类型中排除掉 string类型
type myType = Exclude<string | number, string>

let e: myType = 10
// 报错
let e1: myType = '10'
console.log(e) // 10
1
2
3
4
5
6
7

# 👉8.8.2 Extract(提取)

  • 从某些类型中, 提取某些类型, 语法同 Exclude 差不多
  • 语法: Exclude<多个类型, 提取的哪些类型>
// 提取string 类型
type str = Extract<string | number, string>
// 使用
let e: str = '1'

// 报错, 因为只有string
let e2: str = 1
1
2
3
4
5
6
7

# 👉8.8.3 NonNullable

  • 排除掉 null 和 undefind
type E = NonNullable<string | number | null | undefined>
let e: E = null
1
2

# 👉8.8.4 ReturnType

  • 获取函数类型的返回类型
function getUserInfo() {
  return { name: 'hello', age: 15 }
}

// 通过 ReturnType 将 getUserInfo 的返回值类型赋给了 UserInfo
type UserInfo = ReturnType<typeof getUserInfo>

const userA: UserInfo = {
  name: 'hello',
  age: 10
}
1
2
3
4
5
6
7
8
9
10
11

# 👉8.8.4 InstanceType 实例类型

  • 获取构造函数类型的实例类型
class Person {
  name: string
  constructor(name: string) {
    this.name = name
  }
  getName() {
    console.log(this.name)
  }
}

type P = InstanceType<typeof Person>

let p: P = { name: 'zhufeng', getName() {} }
1
2
3
4
5
6
7
8
9
10
11
12
13

# 9. 模块 VS 命名空间

模块

  • 模块是 TS 中外部模块的简称,侧重于代码和复用
  • 模块在期自身的作用域里执行,而不是在全局作用域里
  • 一个模块里的变量、函数、类等在外部是不可见的,除非你把它导出
  • 如果想要使用一个模块里导出的变量,则需要导入

命名空间(一般用不到)

  • 在代码量较大的情况下,为了避免命名空间冲突,可以将相似的函数、类、接口放置到命名空间内
  • 命名空间可以将代码包裹起来,只对外暴露需要在外部访问的对象,命名空间内通过 export 向外导出
  • 命名空间是内部模块,主要用于组织代码,避免命名冲突
export namespace zoo {
    export class Dog { eat() { console.log('zoo dog'); } }
}
export namespace home {
    export class Dog { eat() { console.log('home dog'); } }
}
let dog_of_zoo = new zoo.Dog();
dog_of_zoo.eat();
let dog_of_home = new home.Dog();
dog_of_home.eat();
1
2
3
4
5
6
7
8
9
10
import { zoo } from 'file_path'
let dog_of_zoo = new zoo.Dog()
dog_of_zoo.eat()
1
2
3

# 10. 啥时间使用 type 和 interface

区别

  • type 和 interface 怎么使用呢, 开发中这俩用的最多的应该是 interface
  • type 就是用来定义类型的, 不要把它 和定义变量的 let var const 混淆
  • 你可以把 type 理解为,你可以自己定义任何 js ts 支持的数据类型,或者组合一些复杂的类型
  • interface 有时和 type 是一样的功能
  • interface 再类中使用需要实现
  • 他俩都可以修饰泛型

接口泛型 和 type 泛型 混合使用

// 使用接口
interface A {
  name: string
  age: number
}

function num<T>(a: T): T {
  return a
}
let s: A = {
  name: "lisi",
  age: 12
}
num<A>(s)

// 使用 type

type B = {
  name: string
  age: number
}

function fnB<T>(b: T): T {
  return b
}

let p: B = {
  name: "lisi",
  age: 12
}

fnB<B>(p)

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

使用 type

// 内置的string 等基本类型
let a1: string = 'a1'
//上边这种也可以改写为下边:
type a = string
let a: a = 'hello'
console.log(a) // hello

// 定义一个函数类型
type funType = (name: string, num: number) => string

let fn: funType = function(name: string, num: number): string {
  return name + num
}
console.log(fn('10', 10)) // 1010

// 定义一个对象
type objType = {
  name: string
}
let obj: objType = {
  name: '定义对象'
}
console.log(obj) //{ name: '定义对象' }

// 定义复杂的类型的组合
function myFun() {
  return { name: '', age: 1, list: [] }
}
// s类型就是函数返回一个对象
type s = typeof myFun

let b: s = () => ({
  name: 'lisi',
  age: 12,
  list: []
})
// 也可以修饰泛型...
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

使用 interface, 它有时和 type 实现的功能一样

interface A {
  name: string
  age: number
}
let a: A = {
  name: "我的",
  age: 12
}
console.log(a) // { name: '我的', age: 12 }

interface discount {
  (price: number): number
}
// 修饰函数
let cost: discount = function(price: number): number {
  return price * 0.8
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

那么,还是没有说到底啥时间用他俩, 其实它俩差不多, 只不过个别时候实现时有些不一样, 所以这俩东西,你哪个顺手, 就用哪个