TypeScript 的 public、private、protected、#private、readonly 和抽象类有什么区别?
先理解原理和示例,再练习完整口述与追问。
回答要点
public 默认可访问;protected 只能在类及子类内部访问;private 只能在声明它的类内访问;这三者只在编译期检查,运行时仍可访问。#field 是 JavaScript 原生私有字段,运行时真正不可访问。readonly 只禁止重新赋值。abstract 类不能实例化,抽象方法由子类实现。参数属性可在构造函数参数上直接声明字段。
原理拆解
TypeScript 的访问修饰符在编译后被擦除,obj['secret'] 或 as any 可以绕过检查。#secret 由 JavaScript 引擎保证私有,子类和外部都无法访问,#secret in obj 可用于品牌检查;代价是无法被 Proxy 透明代理,也不支持 Vue 的响应式代理访问。
readonly 是浅层的,对象属性内部仍可修改,需要深层只读可用 Readonly<T> 递归类型或 as const。抽象类可以包含实现代码和构造逻辑,接口只描述结构;类可以实现多个接口,但只能继承一个类。override 关键字配合 noImplicitOverride 能在父类方法改名时报错。
示例与推演
abstract class Storage<T> {
constructor(protected readonly namespace: string) {}
abstract read(key: string): T | undefined
protected key(key: string) { return `${this.namespace}:${key}` }
}
class MemoryStorage<T> extends Storage<T> {
#data = new Map<string, T>()
override read(key: string) { return this.#data.get(this.key(key)) }
write(key: string, value: T) { this.#data.set(this.key(key), value) }
}
constructor(protected readonly namespace: string) 是参数属性,等价于声明字段并在构造函数中赋值。
易错点
不能把 private 当作安全边界,敏感数据必须在服务端保护;用 useDefineForClassFields 时,字段初始化顺序与旧编译方式不同,可能影响父类构造函数中调用的被重写方法。
参考回答可直接练习口述
public 是默认的,protected 允许子类访问,private 只能本类访问,但这三个只是编译期检查,编译后会被擦掉,运行时能绕过。#开头的字段是 JS 原生私有,运行时真正访问不到。readonly 只禁止重新赋值,是浅层的。抽象类不能实例化,可以带实现,抽象方法由子类实现;接口只描述结构,一个类能实现多个接口。构造函数参数加修饰符就是参数属性,可以省掉字段声明。