Initialize Simple Variables on the Prototype

Topics: General
May 23, 2013 at 3:40 PM
Edited May 23, 2013 at 7:45 PM
In order to reduce memory usage and maybe improve initialization time of objects, has there been any discussion about putting simple values (of string/boolean/number) on the prototype of the class?


TS Input
class Foo {
    a:bool = true;
    c:string = "hello world";
    d:number = 321;
}

class Bar extends Foo {}

var a = new Bar();
TSC Output
var __extends = this.__extends || function (d, b) {
    function __() { this.constructor = d; }
    __.prototype = b.prototype;
    d.prototype = new __();
};
var Foo = (function () {
    function Foo() {
        this.a = true;
        this.c = "hello world";
        this.d = 321;
    }
    return Foo;
})();
var Bar = (function (_super) {
    __extends(Bar, _super);
    function Bar() {
        _super.apply(this, arguments);
    }
    return Bar;
})(Foo);
var a = new Bar();
Expected Output
var __extends = this.__extends || function (d, b) {
    function __() { this.constructor = d; }
    __.prototype = b.prototype;
    d.prototype = new __();
};
var Foo = (function () {
    function Foo() {}
    // simple JS types like strings, numbers and booleans on the prototype
    Foo.prototype.a = true;
    Foo.prototype.c = "hello world";
    Foo.prototype.d = 321;
    return Foo;
})();
var Bar = (function (_super) {
    __extends(Bar, _super);
    function Bar() {}
    return Bar;
})(Foo);
var a = new Bar();
If the output from TSC is by design/spec then maybe this can be added via a variable declaration attribute.

I've been implementing this hack but I'm sure I shouldn't be doing this..

Hack

class Foo {

    a:bool;
    c:string;
    d:number;

    static STATIC_CONSTRUCTOR = function(obj) {
        obj.a = true;
        obj.c = "hello world";
        obj.d = 321;
        return true;
    };

    static STATIC_CONSTRUCTOR_INIT = Foo.STATIC_CONSTRUCTOR(Foo.prototype);

}
Thank you.
Developer
May 23, 2013 at 7:57 PM
Putting variable declarations on the prototype vs the instance variable is completely different semantically. As you've noticed, if you want to optimize for memory usage you can do so explicitly if the semantics match what you desire. Consider if your string property c was instead called name. Do you really want the same 'name' property for every instance of Foo? Maybe you do, if so, you need to make that explicit. How would the compiler automatically know which one you meant? The variable declaration attribute you're asking for is essentially what static already is, it defines things shared between all instances of a class rather than those that exist per instance.
May 23, 2013 at 8:38 PM
Edited May 23, 2013 at 8:39 PM
Dan,

I've read your response multiple times, but it's not making much sense to me.

Sharing between objects is exactly what prototypal inheritance provides. When I have a function on a prototype, say that function is named name(),that function is shared between all instances and instances of derived classes. That's the default for methods/functions, why not the default for initialization of simple variables (string/numbers/booleans)? We don't make all our methods static do we.

So yes, I do want name shared between all my instances. It achieves the same thing as initializing variables in the constructor chain. I'm not looking for static functionality since I want the instances to be able to update those values. If you believe that instances updating those values will update those values for all instances, then you need to read up again on prototypal inheritance.

To be clear, I'm not requesting a declaration attribute, but it may provide a pathway to a compromise. And I'm not recommending that objects and arrays be initialized in the same manner.

// TS
class Foo {
    // initializing variables, all instances for Foo will start in this state
    a:bool = true;
    c:string = "hello world";
    d:number = 321;
    name:string = "bob";
}

// TSC Output
var Foo = (function () {
    function Foo() {
        // initializing variables at instance construct time
        // this code is run for every instance created
        this.a = true;
        this.c = "hello world";
        this.d = 321;
        this.name = "bob";
    }
    return Foo;
})();

// Desired TSC Output
var Foo = (function () {
    function Foo() {}
    // initializing variables on the prototype
    // this code is run once
    Foo.prototype.a = true;
    Foo.prototype.c = "hello world";
    Foo.prototype.d = 321;
    Foo.prototype.name = "bob";
    return Foo;
})();
May 24, 2013 at 2:49 PM
Edited May 24, 2013 at 2:51 PM
@pixel4, in your simple example things work rather well.

However, consider what happens here
class Foo {
   name = "Foo";
}

class Bar {
    foo = new Foo();
}

var barOne = new Bar();
var barTwo = new Bar();

barOne.foo.name = "Baz";

console.log(barTwo.foo.name); // Do we really want this to be "Baz"?
I believe the radical move by TypeScript to drop properties on prototypes is a good one. It's a pretty confusing aspect of JavaScript.

Yes, the memory footprint is greater on account of this, but with careful design that can be minimised.
May 24, 2013 at 3:53 PM
Edited May 24, 2013 at 3:54 PM
@nabog

Did you even try running that code? I did ..

Modified TSC output to put name on the prototype

var Foo = (function () {
    function Foo() {}
    Foo.prototype.name = "Foo";
    return Foo;
})();
var Bar = (function () {
    function Bar() {
        this.foo = new Foo();
    }
    return Bar;
})();
var barOne = new Bar();
var barTwo = new Bar();
barOne.foo.name = "Baz";
console.log(barTwo.foo.name);
I get "Foo" from console.log not "Baz". You're understanding of prototypal inheritance is not complete. If you put a value on a prototype, it becomes the default until you set it on the derived object.

Maybe this will clear it up ...

var barTwo = new Bar();

/*
[inspect  barTwo]
    >barTwo:BarInstance
        >foo:FooInstance
            > __proto__
                > name:"Foo"  <---` barTwo.foo.name` shall resolve to this value
*/

barOne.foo.name = "Baz";

/*
[inspect  barTwo]
    > barTwo:BarInstance
        > foo:FooInstance
            > name:"Baz"  <--- `barTwo.foo.name` shall resolve to this value
            > __proto__
                > name:"Foo"  
*/

May 24, 2013 at 4:29 PM
Edited May 24, 2013 at 4:30 PM
Thank you for your kind enquiry regarding my understanding of prototypal inheritance.

Your JavaScript for Bar is faulty.

It should be this:
var Bar = (function () {
    function Bar() {
    }
    Bar.prototype.foo = new Foo();
    return Bar;
})();

May 24, 2013 at 4:37 PM
@nabog Huh? If you read back in this thread, I suggested that only simple types (string/numbers/boolean) should be placed on the prototype. Objects and Arrays should not be placed on the prototype. Please read the thread.
May 24, 2013 at 5:00 PM
Yes, I see that you did mention that. So my mistake.

Bear in mind this will only be beneficial if the properties are never assigned to in an instance constructor, because that will create a new property on the instance and the benefit is lost.

We personally do not have a use-case for classes with properties that are initialised with literals such as true, "hello world", 321 and "bob" and never changed thereafter.
May 24, 2013 at 5:49 PM
Here's a use-case ..

TS Input
class Vec4 {
    x:number = 0;
    y:number = 0;
    z:number = 0;
    w:number = 0;
}
TSC Output
var Vec4 = (function () {
    function Vec4() {
        this.x = 0;
        this.y = 0;
        this.z = 0;
        this.w = 0;
    }
    return Vec4;
})();
This is a common type of data object when working in high performance environments such as WebGL. If I create an instance, the constructor will create and assign 4 number values each time.

What you're saying is that if we change the constructor to something like this ...
class Vec4 {
    constructor(public x=0, public y=0, public z=0, public w=0) {}
}
... then the constructor has to be involved with the assignment; the prototype value becomes useless and the output from TSC becomes ...
var Vec4 = (function () {
    function Vec4(x, y, z, w) {
        if (typeof x === "undefined") { x = 0; }
        if (typeof y === "undefined") { y = 0; }
        if (typeof z === "undefined") { z = 0; }
        if (typeof w === "undefined") { w = 0; }
        this.x = x;
        this.y = y;
        this.z = z;
        this.w = w;
    }
    return Vec4;
})();
.. but if we used the prototype to capture the default values, then TSC could produce code like this ..
var Vec4 = (function () {
    function Vec4(x, y, z, w) {
        if (x!=null) this.x = x;
        if (y!=null) this.y = y;
        if (z!=null) this.z = z;
        if (w!=null) this.w = w;
    }
    Vec4.prototype.x = 0;
    Vec4.prototype.y = 0;
    Vec4.prototype.z = 0;
    Vec4.prototype.w = 0;
    return Vec4;
})();
Something that I've been thinking about is how values are initialized in the constructor chain. This might be the only side-effect of having simple values on this prototype...

TS Source
class Foo {
    public x = 123;
    constructor() {
        console.log("Foo",this.x);
    } 
}

class Bar extends Foo {
    public x = 321;
    constructor() {
        super();
        console.log("Bar", this.x);
    } 
}

var a = new Bar();
TSC Ouput
var __extends = this.__extends || function (d, b) {
    function __() { this.constructor = d; }
    __.prototype = b.prototype;
    d.prototype = new __();
};
var Foo = (function () {
    function Foo() {
        this.x = 123;
        console.log("Foo", this.x);
    }
    return Foo;
})();
var Bar = (function (_super) {
    __extends(Bar, _super);
    function Bar() {
        _super.call(this);
        this.x = 321;
        console.log("Bar", this.x);
    }
    return Bar;
})(Foo);
var a = new Bar();
This output of console.log is
Foo 123
Bar 321
If we moved TSC to put simple values on the prototype, then this behavior changes


Modified TSC Ouput
var __extends = this.__extends || function (d, b) {
    function __() { this.constructor = d; }
    __.prototype = b.prototype;
    d.prototype = new __();
};
var Foo = (function () {
    function Foo() {
        console.log("Foo", this.x);
    }
    Foo.prototype.x = 123;
    return Foo;
})();
var Bar = (function (_super) {
    __extends(Bar, _super);
    function Bar() {
        _super.call(this);
        console.log("Bar", this.x);
    }
    Bar.prototype.x = 321;
    return Bar;
})(Foo);
var a = new Bar();
The output is now
Foo 321
Bar 321
I'm not sure if this is a good thing or a bad thing. It will cause differences between simple values and Objects/Arrays that can't be initialize on the prototype.


I still think there is merit in discussing optimizing the output from TSC.
Developer
May 24, 2013 at 6:41 PM
We've had a lot of discussions about this topic in the TypeScript design group. It is obviously desirable to share as much as possible through prototypes, but it is an area that is steeped in subtleties to say the least. For example, because it is really only safe to share immutable values, we could only do this for primitives, as you propose. But it would cause seemingly similar declarations of properties with initial values to generate different code and have different observed behavior in several common scenarios. For example, it is very common to enumerate the properties of an object in conjunction with an obj.hasOwnProperty(...) check, and this behaves differently for instance vs. prototype properties.

So far our conclusion has been that it's better to have a single predictable behavior here.

BTW, a convenient way to initialize properties on the prototype is to simply do it with assignment statements following a class declaration. Referring to your initial example, you could simply write:
class Foo {
    a: bool;
    c: string;
    d: number;
}
Foo.prototype.a = true;
Foo.prototype.c = "hello world";
Foo.prototype.d = 321;
This is still fully typed checked (because the prototype member is strongly typed) and makes it pretty clear what's going on.
May 24, 2013 at 7:27 PM
Hi @ahejlsberg. It's good to know that this has been discussed. And think you for the prototype tip, it does make my hack look a little silly now lol.

I guess my hope for TypeScript is that I wouldn't have to worry/know about the prototype object at all. And issues with hasOwnProperty enumeration would be taken care of via TypeScript constructs. shrug

I would counter the obj.hasOwnProperty(...) example by suggesting that maybe it's useful to know if a value has been set on an object or is it the prototype value I'm getting.


I know it's not the most brilliant idea but you could keep a list of enumerable properties on the class prototype.
class Foo {
    b=123;
}
class Bar extends Foo {
    a="hello world"
}
var __extends = this.__extends || function (d, b) {
    function __() { this.constructor = d; }
    __.prototype = b.prototype;
    d.prototype = new __();
};
var Foo = (function () {
    function Foo() {
        this.b = 123;
    }
    Foo.prototype.__enums = ["b"];
    return Foo;
})();
var Bar = (function (_super) {
    __extends(Bar, _super);
    function Bar() {
        _super.apply(this, arguments);
        this.a = "hello world";
    }
    Foo.prototype.__enums = ["a"].concat(_super.prototype.__enums);
    return Bar;
})(Foo);
Then somehow enable a forIn on TypeScript class instances or just let people access the __enums object directly.

var a = new Bar();
a.forIn(function(propName) {});

Meh.


Anyhow, thanks for the feedback. I'm a big TS fan, been working with it everyday for many weeks now. When's 0.9 out of alpha/beta :P ?


Thanks again.
May 24, 2013 at 8:43 PM
Here's another inconsistency with this suggestion:
class Bar {
}

class Foo {
    x:number = 50;
    bar = new Bar();
}

var foo = new Foo();

delete foo.x;
foo.x.toString(); // okay

delete foo.bar;
foo.bar.toString(); // Ag!

Developer
May 30, 2013 at 12:15 AM
pixel4 wrote:
I guess my hope for TypeScript is that I wouldn't have to worry/know about the prototype object at all. And issues with hasOwnProperty enumeration would be taken care of via TypeScript constructs. shrug
As one of our goals is to be able to use Typescript to model as many of the common, existing JavaScript patterns as possible this would very much be at odds with that. Your Typescript code also needs to be able to interop with JavaScript as you'd expect, which may use the prototype in all sorts of different ways. As you've noted, there're very real differences between putting things on the prototype or not, both from a semantic and performance perspective. I'm not sure how you envision the language hiding this abstraction while also exposing the types of tricks you want to have the ability to express. Using Ander's example, how would you see this being interpreted by the compiler?
class Foo {
    a = true;
    c = 'hello world';
    d = 321;
}
How would the compiler know whether to emit
var Foo = (function () {
    function Foo() {
        this.a = true;
        this.b = 'hello world';
        this.c = 321;
    }
    return Foo;
})();
or
var Foo3 = (function () {
    function Foo() {
    }
    return Foo;
})();
Foo3.prototype.a = true;
Foo3.prototype.c = "hello world";
Foo3.prototype.d = 321;
The two JavaScript options have completely different characteristics. You would surely need some syntax to support the distinction, which is basically the case day, albeit perhaps not as concise as you'd like. That's what I was trying to get at with my initial reply.
Sep 20, 2013 at 12:18 PM
It is really disappointing that this discussion has come to an end. After taking a closer look at TypeScript over the last weeks, this seems to be a serious flaw in TypeScript.

tl;dr: TypeScript is really missing the ability to set properties on the prototype not only as this is a common pattern in JavaScript but also as it would introduce useful new features. I would suggest to introduce a new keyword to make this possible.

Prototype properties in JavaScript

There are a lot of JavaScript libraries out there that rely on properties set on the prototype and also for TypeScript developers this would be a great feature. For example lets look how the popular Bootstrap.js library handles views. If you want to create your own view, you simply write:
var MyButton = Backbone.View.extend({
    caption: 'Submit',
    tagName: 'button',
    className: 'btn',
    events: { 'click':'onClick' },
    onClick: function(event) { console.log(this.caption + ' clicked!'); }
});
var button = new MyButton();
The properties ´tagName´, ´className´ and ´events´ are set on the prototype, which makes sense as:
  • They will not change in different instances of the class.
  • They consume less memory than instance properties, especially when a lot of instances are created.
  • The underlying constructor will access them to create the desired element and therefore needs the values right after instantiation.
Generally spoken, properties on prototypes behave like a mix of instance (set on a instance through ´this´) and static (set on a constructor, e.g. ´MyButton´) properties:
  • They can be accesses like instance properties but are shared among all instances. Static properties can only be accessed through the constructor instance.
  • They are immediately available after instantiation, they don't need to be initialized like instance properties.
  • They respect class inheritance. Both instance and static properties are only available on the objects they are set on. Prototype properties always return the closest value from the inheritance chain.

Prototype properties in TypeScript

The only way to set prototype properties with TypeScript is to apply them to the prototype of a constructor after the class has been declared, as ahejlsberg describes in his post above.
class MyButton extends Backbone.View{
    caption:string = 'Submit';
    tagName:string;
    className:string;
    events:Object;
    onClick(event) { console.log(this.caption + ' clicked!'); }
}
MyButton.prototype.tagName = 'button';
MyButton.prototype.className = 'btn';
MyButton.prototype.events = { 'click':'onClick' };
var button = new MyButton();
However this leads to unreadable code, especially on large classes, and undermines the class syntax of TypeScript. And after all the complaints of the possible inconsistencies introduced by this approach in this discussion, it does not document the developers intention as it does not clearly mark the property as being a property on the prototype.

Proposal

To solve this problems I would recommend to introduce a new keyword for marking properties in classes that should be set on the prototype and not on the instance. The keyword should express that the defined property is used by all instances of the class and clearly express the developers intention, therefore I would recommend ´shared´ or ´mutual´.

The above example would look like this:
class MyButton extends Backbone.View{
    caption:string = 'Submit';
    mutual tagName = 'button';
    mutual className = 'btn';
    mutual events = { 'click':'onClick' };
    onClick(event) { console.log(this.caption + ' clicked!'); }
}
And should yield the following JavaScript code:
var MyButton = (function (_super) {
    __extends(MyButton, _super);
    function MyButton() {
        _super.apply(this, arguments);
        this.caption = 'Submit';
    }
    MyButton.prototype.tagName = 'button';
    MyButton.prototype.className = 'btn';
    MyButton.prototype.events = { 'click': 'onClick' };
    MyButton.prototype.onClick = function (event) {
        console.log(this.caption + ' clicked!');
    };
    return MyButton;
})(Backbone.View);
TypeScript should not try to solve inconsistencies introduced by JavaScript nor should it hide available features from the developer.

Antipattern shown in the examples

Even more dangerous is the approach taken by the TodoMVC example (https://typescript.codeplex.com/sourcecontrol/latest#samples/todomvc/js/todos.ts). As the developers must have stumbled upon the point of missing prototype properties, they simply set them as instance properties:
class TodoView extends Backbone.View {
    // [...]
    constructor(options?) {
        this.tagName = "li";
        this.events = {
            "click .check": "toggleDone",
            "dblclick label.todo-content": "edit",
            "click span.todo-destroy": "clear",
            "keypress .todo-input": "updateOnEnter",
            "blur .todo-input": "close"
        };
        super(options);
        // [...]
    }
    // [...]
}
On the first glimpse this looks okay, but it introduces a series of problems:
  • It might be tempting to set these values as default values on the class properties (actually in the same example the class ´TodoList´ does this with the prototype ´model´ property) but this does not work as TypeScript sets default values after ´super()´ has been called. However, Bootrsap.js relies on the values being set on the prototype and therefore accesses them right within the constructor, leading to the unexpected behavior of the default values being ignored.
  • As you must set the values before the call to ´super()´ in the constructor, you actually cannot use TypeScript's default values in this class anymore.
  • Inheritance is completely ignored and classes written like this cannot be extended. Imagine you want to create a subclass of ´TodoView´, you normally would like to write something like this:
    class StaticTodoView extends  TodoView {
        constructor(options?) {
            this.events = { "click":"showDetails" };
            super(options)
        }
    }
    
    As the constructor of ´TodoView´ will always overwrite ´this.events´, you have no chance to change the behavior of the view in sublasses. To get past this problem one would had to implement additional checks for whether the property is already set or not.
  • Each time you create an instance of ´TodoView´ unnecessary objects will be created for setting the ´events´ property, all containing the exact same data. At least you should declare a static value and reuse that.
Don't set properties on instances when prototype properties are required. Use the approach given by ahejlsberg
Sep 20, 2013 at 1:35 PM
Hi,
I completely agree with current implantation as described by ahejlsberg, but syntactical sugar as proposed by DerCapac is also welcome. But rather than introducing new keyword "mutual" I would prefer 'prototype constructor', so the code will look like this:
class Foo {
    prototype() {
        a = true;
        c = "hello world";
        d = 321;
    }
    a: boolean ;
    c: string;
    d: number = 4;
}
tcs can then generate correct code and ts source is much more readable. The prototype constructor is someway similar to static constructor, it is called once per class, but it can only contains properties assignments.

The other solution (not excluding first one) is to use static constructor, but then the syntax for properties setting should use prototype. Something like this.
class Foo {
    static constructor() {
        Foo.prototype.a = true;
        Foo.prototype.c = "hello world";
        Foo.prototype.d = 321;
    }
    a: boolean ;
    c: string;
    d: number = 4;
}