MST

星途 面试题库

面试题:TypeScript类型兼容性之复杂类型与逆变协变

在TypeScript中,考虑一个泛型类`GenericClass<T>`,它有一个方法`getValue(): T`。现在有两个类型`SubType`和`SuperType`,`SubType`是`SuperType`的子类型。定义`GenericClass<SubType>`和`GenericClass<SuperType>`,请深入分析它们之间的类型兼容性,包括在赋值操作、函数参数传递等场景下的表现,并结合TypeScript的逆变和协变规则进行详细说明。同时,假设`GenericClass`增加一个方法`setValue(value: T)`,这又会对类型兼容性产生怎样的影响,为什么?
45.4万 热度难度
前端开发TypeScript

知识考点

AI 面试

面试题答案

一键面试

1. 仅有 getValue(): T 方法时的类型兼容性分析

  • 赋值操作
    • 在TypeScript中,当一个类只有读取操作(如 getValue)时,它遵循协变规则。也就是说,GenericClass<SubType>GenericClass<SuperType> 的子类型。可以将 GenericClass<SubType> 的实例赋值给 GenericClass<SuperType> 类型的变量。
    • 例如:
class SuperType {}
class SubType extends SuperType {}

class GenericClass<T> {
    getValue(): T {
        // 这里只是示例,实际返回值需要符合类型
        return null as any;
    }
}

let subClass: GenericClass<SubType> = new GenericClass<SubType>();
let superClass: GenericClass<SuperType> = subClass; // 这种赋值是允许的
  • 函数参数传递
    • 在函数参数位置,如果一个函数接受 GenericClass<SuperType> 类型的参数,那么也可以传递 GenericClass<SubType> 的实例。这同样是因为协变规则。
    • 例如:
function acceptSuperTypeClass(cls: GenericClass<SuperType>) {
    // 函数体
}

let subClass: GenericClass<SubType> = new GenericClass<SubType>();
acceptSuperTypeClass(subClass); // 这种传递是允许的

2. 增加 setValue(value: T) 方法后的类型兼容性分析

  • 赋值操作
    • 当类增加了 setValue 这样的写入操作后,它遵循逆变规则。此时,GenericClass<SuperType>GenericClass<SubType> 的子类型,与只有 getValue 方法时情况相反。将 GenericClass<SuperType> 的实例赋值给 GenericClass<SubType> 类型的变量是允许的,但反过来不行。
    • 例如:
class SuperType {}
class SubType extends SuperType {}

class GenericClass<T> {
    getValue(): T {
        // 这里只是示例,实际返回值需要符合类型
        return null as any;
    }
    setValue(value: T) {
        // 这里只是示例,实际实现可能不同
    }
}

let superClass: GenericClass<SuperType> = new GenericClass<SuperType>();
let subClass: GenericClass<SubType> = superClass; // 这种赋值会报错
let superToSub: GenericClass<SubType> = new GenericClass<SuperType>(); // 这种赋值也会报错

let subClass2: GenericClass<SubType> = new GenericClass<SubType>();
let subToSuper: GenericClass<SuperType> = subClass2; // 这种赋值现在是不允许的
  • 函数参数传递
    • 在函数参数位置,如果一个函数接受 GenericClass<SubType> 类型的参数,那么可以传递 GenericClass<SuperType> 的实例。这是由于逆变规则。
    • 例如:
function acceptSubTypeClass(cls: GenericClass<SubType>) {
    // 函数体
}

let superClass: GenericClass<SuperType> = new GenericClass<SuperType>();
acceptSubTypeClass(superClass); // 这种传递现在是允许的

总结

  • GenericClass 仅有 getValue 方法时,由于协变规则,GenericClass<SubType> 可视为 GenericClass<SuperType> 的子类型,在赋值和函数参数传递上遵循协变特性。
  • GenericClass 增加了 setValue 方法后,由于逆变规则,GenericClass<SuperType> 可视为 GenericClass<SubType> 的子类型,赋值和函数参数传递的兼容性发生反转。这是因为写入操作(setValue)要求类型更加严格,需要确保传入的值与预期类型完全兼容或为其父类型,以避免类型安全问题。