Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript generates javascript code for simple class inheritance

Tags:

typescript

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.

like image 777
g4rce Avatar asked Apr 06 '14 23:04

g4rce


People also ask

Does TypeScript support inheritance?

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.

How do I inherit a class in TypeScript?

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.

Which inheritance is not supported by TypeScript?

TypeScript supports single inheritance and multilevel inheritance. We can not implement hybrid and multiple inheritances using TypeScript.

How do I create an instance of a class in 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.


2 Answers

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).

like image 139
WiredPrairie Avatar answered Sep 16 '22 14:09

WiredPrairie


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.

like image 23
Ryan Cavanaugh Avatar answered Sep 17 '22 14:09

Ryan Cavanaugh