3

A project I'm working on currently has called for an object type Chain that itself produces a type of object Link that only it uses. In previous projects, I had nested the object constructor and prototype of Link inside the constructor for Chain, however, I began to wonder this time if this was the best way to do this as I would actually be appearing to set up each Chain with its' own definition of Link every time.

For clarity, this was the original code I had:

var Chain = function() {
    var self = this;

    var Link = function(key) {
        this.prop = key;
    };
    Link.prototype.attach = function(args) {
        for (let value of args) {
            this[value.prop] = value.value;
        }
    };

    self.links = [];

}
Chain.prototype.add = function(key) {
    this.links.push(new Link(key));
}

I have then begun wondering if I should just store the Link constructor and prototype out in the wild like I have with Chain (for the record, it's not quite out in the wild, it's actually contained within a object) or whether I should use the prototype of Chain to define Link and keep it there.

I've failed to find much information on best practice in this situation, and while I strongly suspect my first way of doing things is not the best/right way; I'm not sure if my alternatives are either.

So I ask, what is the best practice/most efficient/sensible way to do this?

dbr
  • 661
  • 1
  • 8
  • 21

2 Answers2

5

In previous projects, I had nested the object constructor and prototype of Link inside the constructor for Chain, however, I began to wonder this time if this was the best way...

I have then begun wondering if I should just store the Link constructor and prototype out in the wild like I have with Chain

There's a third option: A single Link which is private to Chain:

var Chain = (function() {
    function Link() {
        // ...
    }
    // Link.prototype stuff here

    function Chain() {
        // ...
    }
    // Chain.prototype stuff here

    return Chain;
})();

Now, Chain is public, Link is private, but there's only one Link rather than creating a new one for every Chain.

This works equally well with ES2015+ class syntax:

const Chain = (function() {
    class Link {
        // ...
    }

    class Chain {
        // ...
    }

    return Chain;
})();
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 1
    Now that is an interesting approach that I hadn't considered, and one I like. Curiously, you have also posited answered a second question I've had for a while, which is the tidiest way to define object constructors and their prototypes. Many thanks. – dbr Jun 11 '17 at 21:41
1

I began to wonder this time if this the best way to do this as I would actually be appearing to set up each Chain with its' own definition of Link every time.

Yes, that's exactly the reason not do this. You don't want to create thousands of Link classes when you create a thousand chains. Always place the class next to each other, never one in the constructor of the other.

Sometimes, an instance-specific helper function can be helpful (to set up a link belonging to that particular chain), but one can do that as a factory method and still keep all links from all chains share the same (base) class.

Should I just store the Link constructor and prototype out in the wild like I have with Chain?

Yes, that's fine, as far as scope goes. Unless "the wild" is the global scope, there's nothing wrong with this. If you like to have some encapsulation, you can use ES6 modules to export only the "public" parts of that scope, or use an IIFE (like in @T.J.Crowder's answer).

Another common method that keeps the classes together, having one belong to the other, is to use the class as a namespace object:

var Chain = function() {
    this.links = [];
};
… // Chain.prototype

Chain.Link = function(key) {
    this.prop = key;
};
… // Chain.Link.prototype

The same works with ES6 classes.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375