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
andsuper.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
andDerived
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
andsuper.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
andDerived
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));