I have a question about how Typescript generates javascript code for simple class inheritance. Below is some Typescript code followed by the generated javascript code.
Typescript code:
class Animal {
constructor(public name: string) { }
move(meters: number) {
alert(this.name + " moved " + meters + "m.");
}
}
class Cat extends Animal {
constructor(name: string) { super(name); }
move() {
alert("run...");
super.move(5);
}
}
Generated Javascript code:
var __extends = this.__extends || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
__.prototype = b.prototype;
d.prototype = new __();
};
var Animal = (function () {
function Animal(name) {
this.name = name;
}
Animal.prototype.move = function (meters) {
alert(this.name + " moved " + meters + "m.");
};
return Animal;
})();
var Cat = (function (_super) {
__extends(Cat, _super);
function Cat(name) {
_super.call(this, name);
}
Cat.prototype.move = function () {
alert("run...");
_super.prototype.move.call(this, 5);
};
return Cat;
})(Animal);
You will see that the generated javascript code contains an __extends(d, b)
. This function copies all the base class properties to derived class: d[p] = b[p];
.
My question is why is this copying required, just setting the Cat.prototype = _super;
would had been fine and inline with Javascript prototype based inheritance. Copying the the base ctor properties to derived ctor like d[p] = b[p];
seems wastage of memory.
TypeScript supports the concept of Inheritance. Inheritance is the ability of a program to create new classes from an existing class. The class that is extended to create newer classes is called the parent class/super class. The newly created classes are called the child/sub classes.
To inherit a class, you use the extends keyword. For example the following Employee class inherits the Person class: class Employee extends Person { //.. } In this example, the Employee is a child class and the Person is the parent class.
TypeScript supports single inheritance and multilevel inheritance. We can not implement hybrid and multiple inheritances using TypeScript.
You can do this by using the new keyword, followed by a syntax similar to that of an arrow function, where the parameter list contains the parameters expected by the constructor and the return type is the class instance this constructor returns. The TypeScript compiler now will correctly compile your code.
Static properties/functions do not exist on the prototype. The prototype
is only used when a new
instance is created.
So, the first loop simply copies static properties (which would include functions). It is not copying anything on the prototype
(as for(var v in p)
does not include the prototype
or properties/functions declared on the prototype).
The best way to preserve a prototype
chain in JavaScript is to set the prototype
of the subclass to an instance of the super class. In the case above, it means the equivalent of this:
Cat.prototype = new Animal();
That way, when JavaScript is looking for a matching property, it will properly follow the prototype chain through the inheritance hierarchy.
If you set it directly:
Cat.prototype = Animal.prototype
That would mean that any runtime changes to the Cat
prototype
would also affect the Animal
as they would point to the same instance (which generally would not be desirable).
This is all about static properties on the class. Consider some code:
class Mammal {
static descriptor = 'mammalian';
constructor() {}
getLegs() {
return 4; // Good for most things
}
}
class OtherMammal extends Mammal {
// Default implementation is OK
}
class Human extends Mammal {
static descriptor = 'anthropic'
constructor() {
super();
}
getLegs() {
return 2; // Being bipedal is great
}
}
function createMammal(classType: typeof Mammal) {
console.log('Creating a new ' + classType.descriptor + ' object');
// ...
}
createMammal(Mammal); // "Creating a new mammalian object"
createMammal(Human); // "Creating a new anthropic object"
// Normal version of _extends
createMammal(OtherMammal); // "Creating a new mammalian object"
// If we had removed the line with d[p] = b[p];
createMammal(OtherMammal); // "Creating a new undefined object"
Note the last line - if we don't copy objects from the base class constructor function to the derived class constructor function, we can't use the derived class constructor function in place of the base class constructor function. I'd recommend running this code in a tab and checking out the prototype
and __proto__
properties of each of the classes to better understand exactly what's happening.
The idea in TypeScript is that the constructor function of a derived class is a subtype of the constructor function of its parent class. This allows for factory patterns like the above.
Based on the answers here and the question, it seems like there's some confusion between prototype
and __proto__
. Changing prototype
only changes the __proto__
of objects created via new
; it does not affect the results of property lookups.
The static inheritance in TypeScript is a bit different from static inheritance in C# -- each class in the hierarchy gets its own copy of the static properties. This might be what you expect, or not.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With