TypeScript 面向对象之class(上)

112 阅读1分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第 6 天

思考题:写属性时会覆盖共有属性吗?

function Person(name, age) {
  this.name = name
  this.age = age
}

Person.prototype = {
  constructor: Person,
  sayHi(target) {
    console.log(`你好,${target.name}, 我是${this.name}`)
  }
}

const p0 = new Person('tom', 17)
const p1 = new Person('hone', 18)
const p2 = new Person('frank', 19)

p1.sayHi = function (target) {
  console.log(`${target.name} 你是个憨憨`)
}

p2.sayHi(p0) // 请问这个结果是什么?
// 结果: 你好,tom, 我是 frank

Screen Shot 2022-10-14 at 8.56.02 PM.png

可以看到 p1 身上被加了一个 sayHi, p1 的原型上也有一个 sayHi。

Screen Shot 2022-10-14 at 8.57.16 PM.png

可以看到 p2 的原型上有,本身是没有的。

为什么需要class

有了原型,为什么还要 class?

  1. 外来人口多,迫切需要 class
  2. class 是保留字,迟早要实现 class
  3. 你其实没理解原型

interface和class的区别

// 在关闭严检查后 class 和 interface 几乎一摸一样
interface PointInterface {
  x: number
  y: number
}

class PointClass {
  x: number
  y: number
}

const p = new PointClass()
p.x = 1
p.y = 1

const p2: PointInterface = {
  x: 1, y: 2
}

Screen Shot 2022-10-14 at 9.08.40 PM.png

怎么关闭严格检查? 可以在 tsconfig.json 里添加 "strictPropertyInitialization": false 或者 "strict": false

那如果都不添加,该如何解决?

有如下四种方法,可以解决

这个 ! 的意思就是告诉 TS 别检查,我会确保自己初始化的,如果没有确保自己初始化那就是 bug。


当我们写 interface 的时候,接口里面只能写类型,连默认值都给不了。

但是它既有类型又有值,能不能只有类型没有值呢?不行。

结论

interface 只有成员的类型没有实现

class 须同时有成员的类型和实现

class之构造函数

// 第一种写法
class Point {
  x: number
  y: number
  
  constructor(x=0, y=0){
    this.x = x
    this.y = y
  }
}

const p = new Point()
console.log(p.x, p.y)
// 第二种写法
// 这个语法 JS 没有
// 通过在参数前面加上 public 就可以让它很容易的初始化
class Point {
  constructor(public x: number = 0, public y: number =0) {
  } 
}
const p = new Point()
console.log(p.x, p.y)

以上代码意思相同

// 第三种写法
class Point {
  x!: number
  y!: number
  
  // 重载,先两个假的给用户看,在写真的
  constructor(x: number, y: number)
  constructor(s: string)
  // 这个真的保证能兼容这两个假的
  constructor(xs: number | string, y?: number) {
    if (typeof xs === 'number' && typeof y === 'number') {
      this.x = xs
      this.y = y
    } else if (typeof xs === 'string') {
      const parts = xs.split(',')
      this.x = parseFloat(parts[0])
      this.y = parseFloat(parts[1])
    }
  }
}

const p = new Point('1,2')
console.log(p.x, p.y)
// 第四种写法
class Hash {
  [s: string]: unknown
  
  set(key: string, value: unknown) {
    this[key] = value
  }
  get(key: string) {
    return this[key]
  }
}

class可以实现接口

// 实现一个接口
interface Person {
  name: string
  sayHi: (target: Person) => void
}

class User implements Person {
  constructor(public name: string) {
  }
  sayHi(target: Person) {
    console.log(`Hi ${target.name}`)
  }
}
// 实现两个接口
interface Person {
  name: string
  sayHi: (target: Person) => void
}

interface Taggable {
  tags: string[]
  addTag: (tag: string) => void
  removeTag: (tag: string) => void
}

class User implements Person, Taggable {
  constructor(public name: string){
  }
  tags: string[] = []
  sayHi(target: Person) {
    console.log(`Hi ${target.name}`)
  }
  addTag(tag: string) {
    this.tags.push(tag)
  }
  removeTag(tag: string) {
    // this.tags = this.tags.filter(t => t !== tag)  // 这种写法比较浪费内存
    const index = this.tags.indexOf(tag)
    this.tags.splice(index, 1)
  }
}
interface Person {
  name: string
  age?: number  // 这一行
  sayHi: (target: Person) => void
}

class User implements Person {
  constructor(public name: string) {
  }
  sayHi(target: Person) {
    console.log(`Hi ${target.name}`)
  }
}

const u = new User('hone')
// 问: u.age 的值是多少?
// 1.  0
// 2.  undefined
// 3.  TS 报错

// 答案: 3.  TS 报错

// implemets 不会帮你实现任何东西,只是添加了类型间的关联
// 那么这个 User 类 就没有实现 age
// User类 没实现为什么不会报错啊? 因为它是可选的
// 但在 User 来看,我就是没有 age
// 所以 user.age 就会报错

Screen Shot 2022-10-15 at 11.21.23 AM.png

class能继承class

class Person {
  constructor(public name: string) {}
  sayHi() {
    console.log(`你好,我是${this.name}`)
  }
}
class User extends Person {
  constructor(public id: number, name: string) {
    super(name)
  }
  login(){}
}
const u = new User(1, 'hone')

u.name
u.id
u.sayHi()
u.login()
// 在继承的时候能不能重写 override ?
class Person {
  constructor(public name: string) {}
  sayHi() {
    console.log(`你好,我是${this.name}`)
  }
}
class User extends Person {
  constructor(public id: number, name: string) {
    super(name)
  }
  login(){}
  saiHi(targe?: User) {
    if (target === undefined) {
      super.sayHi()
    } else {
      console.log(`你好,${target.name},我是${this.name]`)
    }
  }
}

什么是重写? 重写同名函数,这个类的方法覆盖了另一类的方法。

// 通过 declare 重写 非函数属性
class Person {
  friend?: Person
  constructor(public name: string, friend?: Person) {
    this.friend = friend
  }
}

// 有的时候我们继承了一个类,但是我们又想把某个属性的类型重新说一下
// 但是它已经有类型了,如果贸然的直接把这个类型重新写一遍会报错
// 所以得加一个关键字告诉 TS 我不是写错了,我是故意要改它的类型,把它的类型給收窄
class User extends Person {
  declare friend: User  // 这里也可以认为是一个断言
  constructor(public id: number, name: string, friend?: User) {
    super(name, friend)
  }
} 

const u1 = new User(1, 'frank')
const u2 = new User(2, 'hone', u1)

u2.friend