TypeScript 面向对象之class(下)

112 阅读6分钟

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

四种成员可见性

  • public 类外可见
  • private 类内可见、#var 真私有属性
  • protected 子类可见
class Person {
  friend?: Person  // 不写就相当于 public friend?: Person
  constructor(public name: string, friend?: Person) {
    this.friend = friend
  }
}

const p = new Person('hone')
// p,friend 可以直接出现在代码提示里面,如果去调用它,虽然是 undefined 但是不会报错
// 这个叫可见
// 可见: 你把它打出来,可以 log 它,可以对它赋值,可以读写。
class Person {
  // 加了 private 在花括号外不可见, private 只能在 这个 class 里面写
  private friend?: Person 
  constructor(public name: string, friend?: Person) {
    this.friend = friend
  }
  xxx(){
    this.friend  //这里不会报错
  }
} //  在这对花括号里面就可见

const p2 = new Person('jack')
// 间接的对 friend 进行赋值
const p = new Person('hone', p2)
// 但是你却读不到它
p.friend  // 报错

Screen Shot 2022-10-15 at 4.07.53 PM.png

Screen Shot 2022-10-15 at 3.53.19 PM.png

class Person {
  protected friend?: Person  // 子类可见, 只有这个家族可以用
  constructor(public name: string, friend?: Person) {
    this.friend = friend
  }
}

const p2 = new Person('jack')
const p = new Person('hone', p2)
// p.friend 不能在外面用

// 可以在 User 里面用
class User extends Person {
  declare friend?: User
  constructor(public id: number, name: string, friend?: User) {
    super(name, friend)
  }
  xxx(){
    this.friend
  }
}

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

u2.friend  // 把 declare friend?: User 删了,这里就不可用了
class Person {
  private friend?: Person 
  constructor(public name: string, friend?: Person) {
    this.friend = friend
  }
}
// 以上代码翻译成 JS 后的样子
"use strict";
class Person {
  constructor(name, friend) {
    this.name = name
    this.friend = friend // 这里依然可以调用
  }
}

如果用 TS 写了一段代码,然后别人不用 TS 引用,用 JS 去引用这个文件,那么它引用的那个代码里面就完全没有类型,虽然这个 friend 是隐藏的(private friend)、私有的,但翻译成 JS 后依然可以去调用。

那该怎么办?那就使用真正的私有属性

class Person {
  #friend?: Person   // 这个 friend 就是真正的私有属性
  constructor(public name: string, friend?: Person) {
    this.#friend = friend
  }
}
// 在外面是访问不到 friend 的
// # 在 TS 里面相当于 private

static属性与static block

  • 通过类名访问
  • 类属性
  • 静态属性
  • 不能有 static name
class Person {
  name: string
  constructor(name: string) {
    this.name = name
  }
}

const p = new Person('hone')
p.name // 我们访问这个 name 得通过对象来访问
// 所以这个 name 叫做 成员属性/对象属性 
// 如果有一个属性要放到 Person 上面呢?
// Person.xxx 这样是不行的
// 通过 static 把对象属性变成类的属性
class Person {
  // 要在里面写 
  // xxx = 1 我这样写不就成了对象的属性了,那该如何?
  static xxx = 1 // 那就加个 关键字,这个意思其实就是 Person.xxx = 1  
  name: string
  constructor(name: string) {
    this.name = name
  }
}

Person.xxx // 这样就可以访问了
const p = new Person('hone')

不能有 static name, 会和自带属性冲突。 Screen Shot 2022-10-15 at 5.17.25 PM.png

Screen Shot 2022-10-15 at 5.20.03 PM.png

在 JS 里面类都是通过函数来模拟的,函数天生都有几个属性。

Screen Shot 2022-10-15 at 5.22.01 PM.png

所以不能声明一个静态属性叫 name,它已经有了,就不要在声明了,同样也不能声明一个叫 prototype、length、arguments、caller、... 的属性会和函数自带属性冲突。

为什么需要 static 属性?

在实现某些功能的时候,有些是独有属性有些是共有属性,那么请问在 class 里面如何实现共有属性?

class Person {
// yyy: string  这种非函数不能实现共有属性,因为只要写在这里就是 这个对象独有属性
  name: string
  constructor(name: string) {
    this.name = name
  }
  // 所有的函数都是对象共有的
  say(){}
}

Person.xxx /
const p = new Person('hone')
// 所以让一个非函数共有 做不到
// 那怎么办?
// 通过类属性来表达原型里面的共有属性这个概念
// 共有的话就是所有对象都有同一个属性,那就把它的属性放到类上面
class Person {
  static xxx = 1  // 意思就是这个 xxx 是我这个对象所有的共有属性
  name: string
  constructor(name: string) {
    this.name = name
  }
  say(){}
}

Person.xxx
const p = new Person('hone')

static block

需求:我想统计一下 Foo 这个功能被用了多少次

// 每重启一次服务 class 就要初始化一遍,把这个类 class 的次数给记下来
class Foo {
  static #count = 0
  constructor(){
    console.log(Foo.#count)
  }
}

// 那么我希望你的 count 是从私有属性去读的,那么这个 let count ... 就超出了 Foo 这个类
// 用不到这个私有属性,于是 Foo.#count 报错
let count = parseInt(localStorage.getItem('count') || '0' ) // 先去看本地有没有存
count += 1
// 等到关机的时候就到 localStorage 里面去, 下次开机我在读

因为需求是要写到私有属性上去的,那怎么办?

class Foo {
  static #count = 0
  // 下面这个意思是这个代码在这个类创建的时候,执行的
  static {
    const count = loadFromLocalStorage() || 0
    Foo.#count += count  // 它就可以访问 Foo.#count 了
  }
  constructor(){
    console.log(Foo.#count)
  }
}


let count = parseInt(localStorage.getItem('count') || '0' )
count += 1

所以 static block 主要是用来初始化私有属性,因为我在外面初始化不了。

类和泛型

class Hash<K, V> {
  map: Map<K, V> = new Map()
  set(key: K, value: V) {
    this.map.set(key, value)
  }
  get(key: K) {
    return this.map.set(key)
  }
}

const h = new Hash<string | number, string | number>()
h.set('name', 'hi')
h.get('name')
class Hash<K, V> extends Map<K, V> {
  destroy() {
    this.clear()
  }
}

class 表达式

const Rectangle = class {  // 可以看成一个匿名的 class
  constructor(public height: number, public width: number) {
  }
  area(){
    return this.height * this.width
  }
}

const r = new Rectangle(100, 200)

抽象类(不常用)

interface A{}  // 只写类型不写实现
// 有的实现有的不实现能不能用 interface ? interface 做不到有的实现
class B{}  // 又写类型又写实现
// 有的实现有的不实现能不能用 class ? class 做不到有的不实现

// 如果希望实现有的实现有的不实现该如何做?
// 加关键字  abstract
// 全部都没实现
interface A {
  name: string
  age: number
}
// 全部都实现了
class B {
  name: string
  age: number
  constructor(name: string, age: number){
    this.name = name
    this.age = age
  }
}

// 折中需求:要求实现一个方法,另外一个方法只写一个类型
abstract class C {
  a() {
    console.log('b')
  }
  abstract b(): number
}

// const c = new C() 这个会报错,不能创建一个抽象类的实例,因为没有实现所以无法 new
// 也可以全部都不实现
abstract class D {
  abstract name: string
  age: number
  constructor(age: number) {
    this.age = age
  }
  
  abstract e(): number
  abstract d(): number
}
// 可以做到把抽象类当接口用
// 抽象的意思是不具体,我不知道它怎么实现
abstract class Base {
  abstract getName(): string
  
  printName() {
    console.log("Hello," + this.getName())
  }
}
const b = new Base()
// 报错: Cannot create an instance of an abstract class.ts(2511)
abstract class C {
  abstract name: string
  age: number
  constructor(age: number) {
    this.age = age
  }
  
  abstract a(): number
  abstract b(): number
}

class D extends C {
  name: string
  constructor(name: string) {
    super(18)
    this.name = name
  }
  a(){
    return 1
  }
  b(){
    return 2
  }
}

const d = new D('hone')

把类当做参数(常用)

把类作为参数而不是把对象作为参数。

// 把 Person 作为参数怎么写?
// 以下为伪代码
class Person {}
function f(x: Person) {
  const p = new x() 
}
f(Person)

// new Person() 这个是对象
// Person 这个是类
class Person {
  constructor(public name: string) {}
}

function f(X: typeof Person) {
  const p = new X('hone')
  console.log(p.name)
}
f(Person)

function f2(X: new (name: string) => Person) {
  const p = new X('hone')
  console.log(p.name)
}
f2(Person)
function f(X: typeof Person) {  // 这里为何要这样写?
  const p = new X('hone')
  console.log(p.name)
}
f(Person)

// typeof Person
function ff(x: typeof 'hello') {}
ff('hello')

// 把 x: typeof 'hello' 改成 x: string
function ff(x: string) {}
ff('hello')
// 通过 ff 我们知道我们传的参数跟参数类型之间不是同等地位,类型总是比参数的具体值要高一个地位
// 比 Person 高的就是 typeof Person 这个类型
function f2(X: new (name: string) => Person) { // 前面的这个 new 的意思它是个类
  const p = new X('hone')
  console.log(p.name)
}
f2(Person)
// Person 的本质是一个函数
// 这个函数会接收一个 name
// (name: string) =>    // Person 会返回一个什么? 
// 会返回一个 Person 的实例
// Person 的本质: (name: string) => p_obj
// p_obj 的类型是 Person

// (name: string) 参数照抄
// 返回值往上提一层 从 'hello' 到 string
function ff2(x: (name: string) => string ){
  x('hone')
}

ff2((name) => 'hello')
class Person {
  constructor(public name: string) {}
  sayHi() {
    console.log(`Hi, I am ${this.name}`)
  }
}

function greet(constructor: new (name: string) => Person) {
  const p = new constructor('hone')
  p.sayHi()
}

greet(Person) // 不报错
greet(new Person('hone')) // 报错
class Person {
  constructor(public name: string) {}
  sayHi() {
    console.log(`Hi, I am ${this.name}`)
  }
}

function greet(constructor: typeof Person) {
  const p = new constructor('hone')
  p.sayHi()
}

greet(Person) // 不报错
greet(new Person('hone')) // 报错