How can I transition my Module-Singleton JavaScript to supporting instances?
Asked Answered
E

2

3

I've been writing an application and I have had a lot of success breaking different pieces of functionality into the so called "Module" pattern where you have a self-executing singleton with public and private members.

var WidgetModule = (function($, options) {

    // Private variable
    var someVar;

    // Private functions
    function somePrivateFunction() {

    }

    // Define the public members
    var self = {
        init: function() {

        },
        someFunction: function() {

        }
    };

    return self;

})(jQuery, options);

I have now run into a case where I have several modules which I would like to be able to create multiple instances of.

I know this pattern is based on the singleton but I wonder if there was a painless way to modify this pattern to support creating instances of them?

Etui answered 16/5, 2011 at 21:13 Comment(0)
D
8

When I need common functionality for multiple objects, here's the pattern I usually use (adjusted to account for the code you presented):

var Widget = (function($) {
    var pubs = Widget.prototype;

    // Private variable -- global to all instances
    var someVar;

    // The constructor    
    function Widget(options) {
        var privateInstanceVar;

        this.privateInstanceFunc = function() {
            return privateInstanceVar;
        };
    }

    // Private functions -- global to all instances
    function somePrivateFunction() {

    }

    // Define the public members
    pubs.init = function() {

    };

    pubs.someFunction = function() {

    };

    return Widget;

})(jQuery);

Usage:

var w = new Widget({someOption: "here"});

As you can see, you can share private data amongst all instances created by the constructor, and if you really want to, you can have private data that's shared only with certain select instance functions. Those functions have to be created in the constructor, which has reuse implications, whereas functions that don't need the truly-private instance data can be on the prototype and therefore be shared by all instances.

Better yet, since you already have a handy scoping function, you can help your tools help you by giving your public functions actual names:

    pubs.init = Widget_init;
    function Widget_init() {

    }

I mostly don't actually code the above, because I've defined a helper factory that makes it a bit more concise (and makes it easier to do specializations of functionality, like a Car inheriting functionality from Vehicle); details here.

Dithyrambic answered 16/5, 2011 at 21:21 Comment(4)
@TJ Is my example too simplistic, something missing? TxCatch
@Mic: I'll post a comment to your answer.Dithyrambic
Why pass jQuery in as $? Isn't it already accessable via $? I am somewhat a JS noob so I am assuming there is something I don't understand.Restricted
@AaronLS: "Why pass jQuery in as $? Isn't it already accessable via $?" Not necessarily, jQuery has a no conflict mode where $ isn't used for jQuery. Since a plug-in is by its nature used in environments the author doesn't have control over, one has to allow for it...Dithyrambic
C
0

What about this:

function WidgetModule(options){
    //var $ = jQuery;
    // Private variable
    var someVar;

    // Private functions
    function somePrivateFunction() {

    }

    // Define the public members
    var self = {
        init: function() {
          console.log(options.id);
        },
        someFunction: function() {

        }
    };

    return self;
}

var w1 = WidgetModule({id:1}),
    w2 = WidgetModule({id:2});

w1.init(); // --> 1
w2.init(); // --> 2
Catch answered 16/5, 2011 at 21:20 Comment(2)
In a comment on my answer, you asked if there was something missing from yours. Fundamentally, it works and should have the output you show. It doesn't have any WidgetModule-wide scope, though (just instance-level scope), so there's no opportunity to share data or functions between instances (what you'd call "class"-level methods/data in a class-based system). Every instance will have its own copy of everything (including function implementations). That's fine for modules and the like, but for objects you're going to have many of, you want to share where you can. (cont'd)Dithyrambic
(continuing) JavaScript has this whole prototype mechanism for doing that (reusing stuff). The above is also a bit unusual; one expects to call a factory via new (e.g., var w1 = new WidgetModule({id:1});). It'll work if you call it via new, but will throw away the object created by new in favor of the one you return; a bit of memory churn (probably largely harmless). The above also breaks instanceof (w1 instanceof WidgetModule will be false). And one of my pet peeves, init and someFunction are anonymous. But it works and I've seen patterns like it advocated before. Best,Dithyrambic

© 2022 - 2024 — McMap. All rights reserved.