面向对象之class

71 阅读3分钟

1. 面向对象之class(一)

1.1 class 与 interface 的区别

/**
 * interface 和 class 的区别: (暂时持疑, 下面代码没有报错)
 * interface 只有成员的类型 没有实现
 * class 必须同时有成员的类型和实现
 */

interface PointInterface {
  x: number
  y: number
}

class PointClass {
  x: number
  y: number
}

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

1.2 class之构造函数

// js 语法
// class Point {
//   x: number
//   y: number

//   constructor(x = 0, y = 0) {
//     this.x = x
//     this.y = y
//   }
// }

// ts语法
// class Point {
//   constructor(public x = 0, public y = 0) {}
// }

// class Point {
//   constructor(public x?: number, public y?: number) {}
// }

// 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)
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]
  }
}

1.3 class可以实现接口继承

interface Person {
  name: string
  age?: number
  sayHi: (target: Person) => void
}
interface Taggable {
  tags: string[]
  addTag: (tag: string) => void
  removeTag: (tag: string) => void
}

class User implements Person, Taggable {
  name: string
  // 实现的类型可以比实现后面的类型小,但是不可以大
  // name: '1' | 'hi'
  tags: string[] = []

  // constructor(public name: string) {}

  addTag(tag: string) {
    this.tags.push(tag)
  }
  removeTag(tag: string) {
    // this.tags = this.tags.filter(item => item !== tag)  // 比较浪费性能
    const index = this.tags.indexOf(tag)
    this.tags.splice(index, 1)
  }
  sayHi(target: Person) {
    console.log(`Hi ${target.name}`)
  }
}

// const u = new User('lwz')
/**
 * u.age 会报错
 * Person 上age是可选属性
 * User 上没有age 属性, 直接报错
 */
// console.log(u.age)

1.4 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() {}
// }
class User extends Person {
  constructor(public id: number, name: string) {
    super(name)
  }
  login() {}
  // 重写(overwrite) 是可以有机会调用父类的方法
  // 重载 (overload) 类型不同/参数个数不同/两个都不同 需要实现不同的方法
  // 重写方法
  sayHi(target?: User) {
    if (target === undefined) {
      super.sayHi()
    } else {
      console.log(` ${target.name} ${this.name}`)
    }
  }
}

const u = new User(1, 'lwz')
u.sayHi()
u.login() */

class Person {
  friend?: Person
  constructor(public name: string, friend?: Person) {
    this.friend = friend
  }
}
class User extends Person {
  // 重写属性 关键字 declare
  // 如果不加, 会发生错误, u2.friend 类型是一个 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(1, 'jack', u1)
u2.friend

2. 面向对象之class(二)

2.1 四种成员可见性

/**
 * public 类外可见
 * private 类内可见 #var 真私有属性
 * protected 子类可见
 */

// 使用private后,经由js类型擦除后类外也是可以访问的,此时就可以使用真私有属性
// class Person {
//   private name: string

//   constructor(name: string) {
//     this.name = name
//   }
// }

class Person {
  #name: string

  constructor(public name: string) {
    this.#name = name
  }
}

2.2 static属性和static block

/**
 * static 关键字
 * 1. 通过类名访问
 * 2. 是一个类属性
 * 3. 静态属性
 * 4. 不能有 name/length/arguments/caller 属性
 */

class Person {
  // static name: string
  static counter: number
  name: string
  constructor(name: string) {
    this.name = name
  }
}

const p = new Person('lwz')
p.name
Person.counter

// static block
class Foo {
  static #count = 0
  static {
    // loadFromStorage 是一个伪方法
    const count = loadFromLocalStorage() || 0
    Foo.#count += count
  }
  constructor() {
    console.log(Foo.#count)
  }
}

2.3 类和泛型

// 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.get(key)
//   }
// }

// const hash = new Hash<string | number | Date | Array<any>, string | number>()
// hash.set('name', 'lwz')
// hash.set([1, 2], 'array')
// console.log(hash.get('name'))

// 此外还可以使用继承
// class 只能继承 class, 但在这里却继承了 interface?
// js可以默认interface 就是 class 暂时的解释 后面需要具体深究
class Hash<K, V> extends Map<K, V> {
  destroy() {
    this.clear()
  }
}

2.4 抽象类

// 匿名class
const Rectangle = class {
  constructor(public height: number, public width: number) {}
  area() {
    return this.height * this.width
  }
}

const r = new Rectangle(100, 200)
console.log(r.area())

// 抽象类
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 {
  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 c = new D('lwz')

2.5 把类当成参数

class Person {
  constructor(public name: string) {}
}

function f(X: typeof Person) {
  const p = new X('frank')
  console.log(p.name)
}
f(Person)
// 解释一下上面的意思
// 声明函数的时候类型要比调用函数的类型要高一个级别
// ff 调用的时候是一个特定的字符串, 而ff在声明的时候需要指定类型为string
function ff(x: string) {}
ff('hello')

function f2(X: new (name: string) => Person) {
  const p = new X('frank')
  console.log(p.name)
}
f2(Person)
// f2 的本质
// (name: string) => p_obj
// 解释一下f2
function ff2(x: (name: string) => string) {}
ff2((name) => 'hello')