3

In John Resig's post on Simple "Class" Instantiation in Javascript, he states:

"...if you have a frequently-accessed function (returning an object) that you want people to interact with, then it's to your advantage to have the object properties be in the prototype chain and instantiate it. Here it is, in code:"

// Very fast
function User(){}
User.prototype = { /* Lots of properties ... */ };
// Very slow
function User(){
    return { /* Lots of properties */ };
}

I would like to apply this to a function like the following (which happens to live inside a "class" declaration)

//...
this.get_subscription = function(args)
{
    return {
        property_one: "value",
        property_two: {
            sub_prop: args.something
        }
    }
}

but have no idea where to put the args. If I do

this.get_subscription = function(args) {}
this.get_subscription.prototype = {
    property_one: "value"
    //...
}

it'll say args are undefined. I've tried several variants, none of which work. How should I properly do this, in a way that doesn't put args in the parent class's scope?

  • 3
    +1 for a well-posed question, but what you're attempting doesn't make much sense if you think about it. There's only one prototype for an object; but the object you're returning is different for every `args`. Is this really a bottleneck in your code? – Cameron May 02 '12 at 23:23
  • Also, a remark to the efficiency you are considered with. The members defined directly in the object are actually quicker to access than those in the prototype. The member lookup first looks into the object, then in its prototype, and so on up the prototype chain. – Imp May 02 '12 at 23:37
  • @Cameron: Not particularly. I just want to have as little JS as possible in this project, and what little I have, I'd like to make as fast as possible :). It seems I misunderstood prototypes, as one of the answers below points out. – Carlos Ramirez May 03 '12 at 14:48

2 Answers2

3

You seem to misunderstand the point of prototype. Prototype holds methods and fields that should be common to all instances of the object. Therefore, if property_two is based upon args passed to the constructor, then it does not belong to the prototype!

After all, in this code

this.get_subscription = function(args) {}
this.get_subscription.prototype = {
    property_one: "value"
    //...
}

first, the function get_subscription is created, then its prototype is set to the object literal. There is no args until you call the constructor, so it does not make sense to do something with the args in the prototype.

So, your usual javascript object should look like this example - a 2D point class.

function Point(x, y) {
    /*  In constructor:
     *  initialize fields that are related to the constructor parameters
     *  and are different for different instances
     */
    this.x = x;
    this.y = y;
}

// in prototype, define fields that are  common to all instances
Point.prototype.RED = '#FF0000';

/*  in prototype, also define the methods; as they can use the "this" keyword,
 *  they can reference their own instance, so you don't have to include them
 *  in each instance manually
 */

Point.prototype.toString = function() { return '[' + this.x + ', ' + this.y + ']'; };
Imp
  • 8,409
  • 1
  • 25
  • 36
1

Yes, that's the drawback of the "everything-to-prototype"-idea: You don't have access to the constructor arguments - they are only available inside the function body (exept you put them into a public property there). Everything that needs to use them does not belong to the prototype; you only will fill "default properties" on the prototype (usually methods that are common to all instances).

I think that you don't need that anyway in here. The pattern is only useful for real constructor functions, which you don't seem to have here (I then would expect this.Subscription). Also, the promised "performance boost" is negligible.

If you really would want to use it in here, it would be:

function Class() {
   this.Subscription = function(args) {
       this.propertyTwo = {
           subprop: args.something
       };
   };
   this.Subscription.prototype = {
       property_one: "value",
       property_two: {}
   };
}
// a method callable without the "new" operator:
Class.prototype.get_subscription = function(args) {
     return new this.Subscription(args);
}

usage:

var object = new Class();
var sub = new object.Subscription();
var sub2 = new object.Subscription();
// the promised boost is less memory usage for:
sub.propertyOne === object.Subscription.prototype.propertyOne === sub2.propertyOne;

Also care for Crockford's Prototypal inheritance - Issues with nested objects when you attempt to set/change/delete properties on the propertyTwo-Object(s).

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Very informative, thank you. Could you elaborate a little on why this pattern produces less memory usage for that comparison? – Carlos Ramirez May 03 '12 at 14:51
  • 1
    No, not for the comparison :) If these properties were objects (also functions) instead of primitive values, the properties would all point to the same object. You need to hold only one instance in memory. Yet, as Imp objected in the comments, the lookup of a prototype property is slower than the one of a direct property. The pattern is only useful when these properties are very memory-consuming. – Bergi May 03 '12 at 17:50
  • 1
    Also, I need to explain the "comparison". The above would expand to false, because `true !== "value"`. What I meant was that `sub.propertyOne === object.Subscription.prototype.propertyOne && object.Subscription.prototype.propertyOne === sub2.propertyOne` is true. – Bergi May 03 '12 at 17:53