7

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.

g4rce
  • 73
  • 4
  • Found this JavaScript **[question](http://stackoverflow.com/a/1535687/3487297)** about static properties, that concurs with @WiredPrairie 's response. – g4rce Apr 07 '14 at 03:10

4 Answers4

5

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

WiredPrairie
  • 58,954
  • 17
  • 116
  • 143
  • Hey but the function __extends is being called with params that are Function objects: `__extends(Cat, Animal)`. If those were `cat = new Cat(); animal = new Animal(); __extends(cat, animal)`, i'd understand what you are saying. Why are we copying `Animal.OwnProperty` function property, instead of the `animal.OwnProperty` object property ? I am new to JS so maybe im missing something obvious here. – g4rce Apr 07 '14 at 01:49
  • That's not how to do inheritance or how one would create what many refer to as static properties. They are functions that, when created with `new`, will create an instance of the function with the `this` set to the new instance. A `function` is just an object, and can be instantiated when used with `new`. If a new instance was instead passed, it would only extend that single instance, not every instance. – WiredPrairie Apr 07 '14 at 01:54
  • Found this JavaScript **[question](http://stackoverflow.com/a/1535687/3487297)** about static properties, that concurs with @WiredPrairie's response. So accepting this as the answer. – g4rce Apr 07 '14 at 03:09
1

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.

Ryan Cavanaugh
  • 209,514
  • 56
  • 272
  • 235
0
d[p] = b[p];.

it is done that way so b own properties are own properties of d.

My question is why is this copying required, just setting the Cat.prototype = _super;

It might seem like a detail but setting "static" properties on a prototype can have undesirable side effects.

imagine you have a static like property on b.

you want d to access this static property without having to call d.prototype.theStaticProperty.

That's how java or C# works.if B is a class that have X as a public static property(or method),then D that extends B has also a public static property or method X.

mpm
  • 20,148
  • 7
  • 50
  • 55
  • Isn't `d.prototype.theStaticProperty` automatically accessible to all objects created using `new d()` constructor function. I think that's already handled by the prototype chaining, and is how inheritance works in JS. Copying the base properties to derived is like overriding every base property instead of sharing them amongst all derived objects. btw, can you elaborate on the undesirable side effects of static properties ? – g4rce Apr 07 '14 at 00:05
  • and what if you want to call d.myStaticProp ? given d a child class of b where b.myStaticProp exists , like in other class based languages ? i'm not talking about instances here,but static properties of classes. You might not agree with this technique but that's another debate,i'm telling you why they do that.that's all. – mpm Apr 07 '14 at 00:21
0

I think copying is required because we need to inherit only methods not properties. In the first line we actually create object properties. Prototype keeps only methods. Second line is required to prevent modifying base class prototype by code like this:

Cat.prototype.catMethod = function() { /* ... */ }

Derived classes need to have their own prototype. We define an anonymouse function. Actually we need its prototype only. this.constructor = d is required if we want to create instances of derived class with correct constructor property. In the third line we set correct prototype for temporary function __. This is actually inheritance of base class methods. In the last line we create prototype of derived class.

alexeibs
  • 535
  • 3
  • 7
  • Thanks, a lot of your analysis makes sense. The one question that I still want to understand is that when `__extends(Cat, Animal)` (where both `Cat` and `Animal` are constructor function objects), why would we copy every `Animal.property` into `Cat.property`. – g4rce Apr 07 '14 at 00:16