TypeScript 学习笔记 — 类以及类的装饰器

时间:2021-1-8 作者:admin

近期在学习TypeScript, 主要原因还是新来的同事说利用TypeScript来开发React Native项目比较方便。目前已经能够开发小型项目(想要玩的很6的话, 还要多写几个项目累积经验)。
个人感受: TypeScript完善了JavaScript弱类型的缺陷, 在编辑器中警告与提示也变得很智能, 本片文章想记录关于类以及类的装饰器的学习笔记, 废话有些多, 正文开始 !?

基本用法

class Greeter {
  greeting: string // 字段是类里面声明的变量 构造函数在类实例化后初始化字段 greeting
  constructor(message: string) {
    this.greeting = message
  }

  greet(event: number = 0): void {
    console.log(`hello ${this.greeting} ${event}`)
  }
}

class Snake extends Greeter {
  constructor(message: string) {
    super(message) // 在构造函数里访问this的属性之前 一定要调用super
  }

  greet(event: number = 5) {
    super.greet(event)
  }
}

let sam = new Snake("Sammy the Python")
sam.greet() // hello Sammy the Python 5

公有, 私用与受保护的修饰符

  1. public(默认): 公有, 可以在任何地方被访问。
class Greeter {  
 public greeting: string  
 public constructor(message: string) {    
  this.greeting = message  
 }  
 public greet(event: number = 0): void {    
  console.log(`hello ${this.greeting} ${event}`)  
 }
}

let sam = new Greeter("Sammy the Python")
console.log("greeting", sam.greeting) // greeting Sammy the Python
  1. private: 私有, 只能被其定义所在的类访问。
class Greeter {  
 private greeting: string  
 public constructor(message: string) {    
  this.greeting = message  
 }  
 public greet(event: number = 0): void {    
  console.log(`hello ${this.greeting} ${event}`)  
 }
}

let sam = new Greeter("Sammy the Python")
console.log("greeting", sam.greeting) // 错误: 'greeting' 是私有的.

官方给出了其它案例 可以更好的理解

class Greeter {  
 private greeting: string  
 constructor(message: string) {    
  this.greeting = message  
 }
}

class Rhino extends Greeter {  
 constructor() {    
  super("Sammy the Python")  
 }
}

class Employee {  
 private greeting: string   constructor(message: string) {     
  this.greeting = message   
 }
}

let greeter = new Greeter("Goat")
let rhino = new Rhino()
let employee = new Employee("Bob")
greeter = rhino
greeter = employee // 错误: greeter 与 Employee 不兼容.

Greeter和Rhino共享了私有成员greeting 因此它们之间是兼容的, 然而Employee和Greeter类型不兼容 两者构造函数中定义的私有成员greeting, 并不是一个。

  1. protected: 受保护, 可以被其自身以及其子类和父类访问。
class Greeter {
  protected greeting: string
  constructor(message: string) {
    this.greeting = message
  }
}

class Rhino extends Greeter {
  constructor(greeting: string) {
    super(greeting)
  }

  public greet(event: number = 0) {
    console.log(`hello ${this.greeting} ${event}`)
  }
}

let sam = new Rhino("Sammy the Python")
sam.greet() // hello Sammy the Python 0
console.log(sam.greeting) // 错误

我们不能在Greeter类外使用name, 但是我们仍然可以通过Rhino类的实例方法访问, 因为Rhino是由Greeter派生而来的。

setter getter 存取器

官方提供案例: 使用场景可以用作权限修改员工

let passcode = "secret passcode"
class Employee {
  private _fullName: string

  get fullName(): string {
    return this._fullName
  }

  set fullName(newName: string) {
    if (passcode && passcode === "secret passcode") {
      this._fullName = newName
    } else {
      console.log("Error: Unauthorized update of employee!")
    }
  }
}

let employee = new Employee()
employee.fullName = "Bob Smith"
console.log("fullName", employee.fullName) // Bob Smith

抽象类

abstract class Department {
  constructor(public name: string) {}

  printName(): void {
    console.log("Department name:" + this.name)
  }

  abstract printMeeting(): void // 必须在派生类中实现
}

class AccountingDepartment extends Department {
  constructor() {
    super("Bob Smith") // 在派生类的构造函数中必须调用 super()
  }

  printMeeting(): void {
    console.log("The Accounting Department meets each Monday at 10am.")
  }

  generateReports(): void {
    console.log("Generating accounting reports...")
  }
}

let department: Department // 允许创建一个对抽象类型的引用
department = new Department() // 错误: 不能创建一个抽象类的实例
department = new AccountingDepartment() // 允许对一个抽象子类进行实例化和赋值
department.printMeeting()
department.generateReports(); // 错误: 方法在声明的抽象类中不存在

抽象方法 只有声明, 没有实现, 不可被实例化, 只能被声明引用, 不能创建对象。
如果一个类中有抽象方法, 这个类就必须是抽象类, 但是抽象类中未必有抽象方法, 抽象类中可以有构造方法。
子类继承抽象类, 如果子类不希望也成为抽象类, 他就必须实现父类中声明的所有抽象方法。
抽象方法只保留方法的功能, 而具体的执行, 交给继承抽象类的子类, 由子类重写此抽象方法。
若子类继承抽象类, 并重写了所有的抽象方法, 则此类是一个”实体类”, 即可以实例化。
若子类继承抽象类, 没有重写所有的抽象方法, 意味着此类中仍有抽象方法, 则此类必须声明为抽象的!

高级技巧 — 构造函数

简单可以理解构造函数类型 类可以创建出类型

class Greeter {
  static standardGreeting = "Hello, there"
  greeting: string

  greet() {
    if (this.greeting) {
      return "Hello, " + this.greeting
    } else {
      return Greeter.standardGreeting
    }
  }
}

let greeter1: Greeter
greeter1 = new Greeter()
console.log(greeter1.greet()) // Hello, there

类当做接口使用

class Point {
  x: number
  y: number
}

interface Point3d extends Point { // 接口继承类Point
  z: number
}

let point3d: Point3d = {
  x: 1,
  y: 2,
  z: 3
}

console.log("point3d", point3d.z) // point3d 3

类装饰器

装饰器本身是一个函数, 装饰器通过 @ 符号来使用, 装饰器接受的参数是类的构造函数。

// 装饰器工厂模式
function testDecorator(flag: boolean) {
  if (flag) {
    return function(constructor: any) {
      constructor.prototype.getName = function() {
        console.log("Bob Smith")      }
    }
  } else {
    return function(constructor: any) {}
  }
}

@testDecorator(true)
class Test {}

const test = new Test()
(test as any).getName() // (写法并不规范 语法提示也不完善)
// 重新定义constructor的类型
// new (...args: any[]) 理解为构造函数 传入形参为数组 数组元素类型为any
// => any 构造函数返回值为any类型
// T 可以理解为类 也可以理解为是包含构造函数的一个东西
function testDecoratorB<T extends new (...args: any[]) => any>(constructor: T) {
  return class extends constructor {
    name = "ooo" // 再执行这里的构造函数
    getName() {
      return this.name
    }
  }
}

@testDecoratorB
class Test {
  name: string
  constructor(name: string) {
    console.log(1) // 先执行这里
    this.name = name
    console.log(2)
  }
}

const test = new Test("Bob Smith")
console.log((test as any).getName()) // ooo
console.log(test.getName()) // 直接获取报错 提示报错原因: class Test父类找不到装饰器testDecorator中的getName方法

以上两种方式并不是很完善 利用类型断言强制定义了类型…下面做出了改善

// 装饰器通俗理解将 js中prototype转换成装饰器的写法
// new (...args: any[]) => any 也可以理解为通过这个泛型实例化 (指向class Test)
const Test = testDecorator()(
  class {
    name: string
    constructor(name: string) {
      this.name = name
    }
  }
)

function testDecorator() {
  return function<T extends new (...args: any[]) => any>(constructor: T) {
    return class extends constructor {
      name = "iii"
      getName() {
        return this.name
      }
    }
  }
}

const test = new Test("Bob Smith")
console.log(test.getName()) // iii
// 这种写法类似闭包

类中方法装饰器

// 普通函数 target对应类的prototype { getName: [Function (anonymous)] }
// 静态方法 target对应类的构造函数 target [Function: Test] { getName: [Function (anonymous)] }
// 例如: static getName() {
//        return "12"
//      }
// key 装饰器方法名(getName)
// descriptor可以理解为Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象
// {
//   value: [Function (anonymous)],
//   writable: true,
//   enumerable: true,
//   configurable: true
// }

function getNameDecorator(
  target: any,
  key: string,
  descriptor: PropertyDescriptor // 形参的解释在?注释
) {
  descriptor.writable = true // 允许外部修改
  descriptor.value = function() {
    return "123"
  }
}

class Test {
  name: string
  constructor(name: string) {
    this.name = name
  }

  @getNameDecorator
  getName() {
    return this.name
  }
}

const test = new Test("Bob Smith")
console.log(test.getName()) // 123

访问器中装饰器

// 同类中方法装饰器形参相同 理解也相同
function visitDecorator(
  target: any,
  key: string,
  descriptor: PropertyDescriptor
) {
  descriptor.writable = true // 允许外部修改
}

class Test {
  private _name: string
  constructor(name: string) {
    this._name = name
  }

  get name() {
    return this._name
  }

  @visitDecorator
  set name(name: string) {
    this._name = name
  }
}

// 访问器中set get 只能定义一个装饰器 具体原因查看文档
const test = new Test("Bob Smith")
test.name = "ooo"

类的属性装饰器

function nameDecorator(target: any, key: string): any {
  console.log(target) // {}
  console.log(key) // name
  // descriptor获取不到 需要自己创建
  const descriptor: PropertyDescriptor = {
    writable: true
  }
  target[key] = "lee" // 此处并没有修改name的值 通过JS角度来看这里是Test.prototype.name 原型链上定义的属性name
                      // 直接对属性的值修改的话 是做不到的
  return descriptor
}
class Test {
  @nameDecorator
  name = "Bob Smith"
}

const test = new Test()
test.name = "rrr"
console.log(test.name) // rrr 通过原型链查找在类的实例上已经找到了name属性
console.log((test as any).__proto__.name) // lee

参数装饰器

function paramDecorator(target: any, method: string, paramIndex: number) {  
 // target { getInfo: [Function (anonymous)] } 原型  
 // method getInfo 方法名  
 // paramIndex 0 参数所在位置  
 console.log(target, method, paramIndex)
}
class Test {  
 getInfo(@paramDecorator name: string, age: number) {    
  console.log("name", name) // name Bob Smith    
  console.log("age", age) // age 24  
 }
}
const test = new Test()
test.getInfo("Bob Smith", 24)

装饰器实用小技巧

// 通用装饰器检测对象是否包含某个属性
let userInfo: any = undefined

function catchError(msg: string) {
  return function(target: any, key: string, descriptor: PropertyDescriptor) {
    const fn = descriptor.value
    descriptor.value = function() {
      try {
        fn()
      } catch (e) {
        console.log(`${msg} 存在问题`)
      }
    }
  }
}

class Test {
  @catchError("userInfo.name")
  getName() {
    return userInfo.name
  }
  @catchError("userInfo.age")
  getAge() {
    return userInfo.age
  }
}

const test = new Test()
test.getName() // userInfo.name 存在问题
test.getAge() // userInfo.name 存在问题

以上是我关于类以及类的装饰器的学习 此外元数据就不做解释了 上述内容如有出错 还望大佬指点迷津?

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。