Home > Mobile >  Effects of redeclaring TypeScript constructor parameter properties during class inheritance
Effects of redeclaring TypeScript constructor parameter properties during class inheritance

Time:08-09

This is a more theoretical question regarding what TypeScript does under the hood.

As specified in this question and based on personal experience, there are two ways of handling constructor parameter properties when inheriting from a class.

Passing existing fields

class Base {
    constructor(public field: string) {}
}

class Derived extends Base {
    constructor(field: string) {
        super(field);
    }
}

Redeclaring existing fields

class Base {
    constructor(public field: string) {}
}

class Derived extends Base {
    constructor(public field: string) {
        super(field);
    }
}

As per the linked question and answer, there are limitations to how the field can be redeclared, but those are clear to me.

My questions are the following:

  • Does the second approach define two fields on the class, where the first is shadowed by the second? Would this.field and super.field evaluate to different attributes of the class?
  • Similarly to the previous question, does the second approach behave differently with methods defined in the Base and Derived classes?

Ultimately it boils down to the question: Are these approaches functionaly identical, or do they behave differently in some edge-cases?

Based on personal experience, the two approaches seem to do the same thing.

I would personaly go with the first approach, as I feel like "nothing can go wrong" and that there will be no hidden pitfalls with this approach, but I would like to understand this better, so that I don't base my programming decisions on vague feelings.

Thank you for your answers.

CodePudding user response:

Does the second approach define two fields on the class, where the first is shadowed by the second? Would this.field and super.field evaluate to different attributes of the class?

No. In JavaScript (and thus TypeScript), an object can only have a single property with any given name.¹ (The super.x() thing works with prototype methods because they're on the prototypes of the Base and Derived classes, not the object [instance].)

Similarly to the previous question, does the second approach behave differently with methods defined in the Base and Derived classes?

I'm afraid I don't understand what you're asking there, but hopefully the following helps answer it.

The redeclaring version is problematic and I'd suggest not using it. To see why, let's look at the generated JavaScript for it (when targeting ES2015 ):

class Base {
    constructor(field) {
        this.field = field;
    }
}
class Derived extends Base {
    constructor(field) {
        super(field);
        this.field = field;
    }
}

Notice how Derived passes field to Base, then overwrites the value Base may have set with its own this.field = field statement.

That's probably okay (but redundant) with the specific definition of Base you've shown, because all that Base does with its field parameter is the same assignment Derived does later. But it would be perfectly reasonable at some point for the author of Base to modify it slightly:

class Base {
    public field: string;
    constructor(field: string) {
        this.field = field.trim(); // Or whatever
    }
}

Now, Base isn't just using field as-is, it's doing something to it before storing the result (trimming whitespace, in that example).

At this point, Derived breaks things, because Base's work is thrown away and replaced by the this.field = field statement in Derived.


¹ That isn't true of some other languages. In Java, for instance, it's possible for one object to have more than one property with the same name declared by different classes in its hierarchy. Which one gets used depends on where the method using it is defined. But not in JavaScript or TypeScript.

CodePudding user response:

As you can see from looking at the compiler output. When redeclaring the field in the derived class, it just adds an additional assignment after calling the constructor of the base class. So if the passed value is modified in the super constructor that will be overwritten by the assignment.

class Base {
    constructor(public field: string) { }
}

class DerivedOne extends Base {
    constructor(field: string) {
        super(field);
    }
}

class DerivedTwo extends Base {
    constructor(public field: string) {
        super(field)
    }
}

Output

var __extends = ...;

var Base = /** @class */ (function () {
    function Base(field) {
        this.field = field;
    }
    return Base;
}());

var DerivedOne = /** @class */ (function (_super) {
    __extends(DerivedOne, _super);
    function DerivedOne(field) {
        return _super.call(this, field) || this;
    }
    return DerivedOne;
}(Base));

var DerivedTwo = /** @class */ (function (_super) {
    __extends(DerivedTwo, _super);
    function DerivedTwo(field) {
        var _this = _super.call(this, field) || this;
        _this.field = field;   // Here the value gets assigned again
        return _this;
    }
    return DerivedTwo;
}(Base));
  • Related