TypeScript:实现接口的类为何不遵循Readonly签名?
为什么ConcreteVisitor不遵守接口的Readonly属性限制?
先看问题中的代码:
class Person { constructor(public age: number, public name: string){} } interface AbstractVisitor { visit(_: Readonly<Person>): void; } class ConcreteVisitor implements AbstractVisitor { visit(p: Person) { p.name = "moish"; } }
这事儿核心原因是TypeScript的类实现接口时的方法参数兼容性规则——默认情况下它用的是「双向协变」,而非严格的函数参数逆变逻辑。
具体拆解:
Readonly<Person>只是把Person的属性标记为只读,但Person类型的实例完全可以赋值给Readonly类型的变量(毕竟可写属性肯定满足「不能被修改」的约束),所以Person是Readonly 的子类型 。- TypeScript为兼容传统面向对象的写法,允许类实现接口时,方法的参数类型既可以比接口要求的更宽泛(逆变),也可以更具体(协变)。所以用Person作为visit方法的参数,TypeScript会判定这是合法实现,编译时不会报错。
但这里藏着运行时隐患:如果把ConcreteVisitor实例当成AbstractVisitor来用,传入一个真正只读的Person实例(比如用Object.freeze(new Person(18, "alice"))创建的对象),运行时修改p.name会直接抛出错误,因为冻结后的对象属性不可写。
如果想要强制严格的类型检查、避免这种隐患,可以开启--strictFunctionTypes编译选项。开启后,TypeScript会对函数/方法参数使用严格的逆变规则,上面的代码会直接编译报错——此时要求实现方法的参数类型必须是接口方法参数类型的超类型(或一致),而Person是Readonly
内容的提问来源于stack exchange,提问作者OrenIshShalom
相关产品推荐
相关产品推荐

