MST

星途 面试题库

面试题:TypeScript装饰器在类继承中的陷阱及解决

当使用TypeScript装饰器装饰一个类,且该类有子类继承时,可能会出现哪些常见陷阱?比如装饰器对继承结构中属性和方法的影响。请详细阐述这些陷阱,并给出优化方案,要求结合实际代码示例说明。
43.1万 热度难度
前端开发TypeScript

知识考点

AI 面试

面试题答案

一键面试

常见陷阱

  1. 属性和方法重写问题
    • 当子类重写父类被装饰的方法时,如果装饰器逻辑依赖于特定的属性或状态,可能会出现意外结果。例如,假设父类方法被装饰为记录调用次数,子类重写该方法后,可能会因为装饰器状态的共享或重置问题导致计数不准确。
    • 示例代码:
function callCountDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    let callCount = 0;
    descriptor.value = function(...args: any[]) {
        callCount++;
        console.log(`Call count for ${propertyKey}: ${callCount}`);
        return originalMethod.apply(this, args);
    };
    return descriptor;
}

class ParentClass {
    @callCountDecorator
    someMethod() {
        console.log('Parent method');
    }
}

class ChildClass extends ParentClass {
    @callCountDecorator
    someMethod() {
        console.log('Child method');
    }
}

const parent = new ParentClass();
parent.someMethod(); // 输出:Call count for someMethod: 1,Parent method
const child = new ChildClass();
child.someMethod(); // 预期是独立计数,但这里由于装饰器逻辑,可能和父类计数产生混淆
  1. 继承链中装饰器的执行顺序
    • 装饰器在继承链中的执行顺序可能不直观。如果装饰器有副作用,比如修改类的原型或添加静态属性,执行顺序可能导致这些副作用在错误的时机发生。
    • 示例代码:
function addStaticPropDecorator(target: any) {
    target.staticProp = 'Added by decorator';
}

@addStaticPropDecorator
class BaseClass {}

class SubClass extends BaseClass {}

console.log(SubClass.staticProp); // 这里期望能访问到静态属性,但由于装饰器执行顺序,可能在子类实例化时还未正确添加
  1. 装饰器状态共享问题
    • 如果装饰器使用了闭包内的状态(如上述的callCount),在继承结构中不同实例间可能会共享这个状态,导致不符合预期的行为。

优化方案

  1. 针对属性和方法重写问题
    • 可以让装饰器在子类重写方法时,保持独立的状态。例如,通过在实例上存储状态而不是在闭包内共享状态。
    • 改进后的代码:
function callCountDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = function(...args: any[]) {
        if (!this[`${propertyKey}_callCount`]) {
            this[`${propertyKey}_callCount`] = 0;
        }
        this[`${propertyKey}_callCount`]++;
        console.log(`Call count for ${propertyKey}: ${this[`${propertyKey}_callCount`]}`);
        return originalMethod.apply(this, args);
    };
    return descriptor;
}

class ParentClass {
    @callCountDecorator
    someMethod() {
        console.log('Parent method');
    }
}

class ChildClass extends ParentClass {
    @callCountDecorator
    someMethod() {
        console.log('Child method');
    }
}

const parent = new ParentClass();
parent.someMethod(); // 输出:Call count for someMethod: 1,Parent method
const child = new ChildClass();
child.someMethod(); // 输出:Call count for someMethod: 1,Child method,计数独立
  1. 针对继承链中装饰器的执行顺序
    • 确保装饰器的副作用在合适的时机执行。可以通过在装饰器内部检查是否为子类,并根据情况调整逻辑。
    • 改进后的代码:
function addStaticPropDecorator(target: any) {
    if (!target.prototype.isSubclass) {
        target.staticProp = 'Added by decorator';
    }
    target.prototype.isSubclass = true;
}

@addStaticPropDecorator
class BaseClass {}

class SubClass extends BaseClass {}

console.log(SubClass.staticProp); // 现在可以正确访问静态属性
  1. 针对装饰器状态共享问题
    • 采用实例级别的状态管理,如上述解决属性和方法重写问题中通过实例属性存储状态的方式,避免状态在不同实例间共享。