基本用法
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
公有, 私用与受保护的修饰符
- 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
- 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, 并不是一个。
- 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 存在问题