Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript Module Pattern create multiple instances using Prototype

What I want to accomplish: Create modules using prototyping in javascript so that a user can instantiate a module multiple times each with different options.

The Problem: when using var my_module3 = new module(); and then trying to set the options with my_module3.init({ option: "value" }); does not change the object each time, it only changes it once.

Testing: When using console.log we can see that it prints out the two objects with the same options even though they are set differently

Object {first: "Barry", second: "Larry", third: "Sam"} 
Object {first: "Barry", second: "Larry", third: "Sam"} 

Here is my jsFiddle full code: http://jsfiddle.net/11bLouc8/2/

        var module = (function () {
        // default options
        var options = {
            first: "test",
            second: "test2",
            third: "test3"
        };
        // take in useroptions and replace default options
        var module = function(userOptions) {
            if (userOptions != null && userOptions != undefined
                && userOptions != 'undefined') {
                for (var opt in options) {
                    if (userOptions.hasOwnProperty(opt)) {
                        options[ opt ] = userOptions[ opt ];
                    }
                }
            }
        };

        //prototype
        module.prototype = {
            init: module,
            options: options
        };

        return module;

    })();

    // create a new instance
    var my_module3 = new module();
    my_module3.init({
        first: "Mike",
        second: "Lisa",
        third: "Mary"
    });

    // another instance
    var my_module2 = new module();
    my_module2.init({
        first: "Barry",
        second: "Larry",
        third: "Sam"
    });
like image 877
AntonB Avatar asked Oct 17 '14 14:10

AntonB


2 Answers

Properties of a function itself behave like static class members (property of a function, not of it's instances)
Properties of function prototype are different across the instances:

function test(){};
test.prototype = {
    constructor : test,
    first : '',
    second : '',
    third : '',
    init : function(f, s, t){
        this.first = f;
        this.second = s;
        this.third = t;
        return this;
    },
    formatToString : function(){
        return 'first : ' + this.first + ', second : ' + this.second + ', third : ' + this.third;
    }
}
var t1 = new test().init(1, 2, 3);
var t2 = new test().init(4, 5, 6);
console.log(t1.formatToString());//outputs "first : 1, second : 2, third : 3"
console.log(t2.formatToString());//outputs "first : 4, second : 5, third : 6" 
like image 184
www0z0k Avatar answered Oct 27 '22 10:10

www0z0k


You're using an immediately-invoked function expression (IIFE), like the Module pattern says you should, but for this case, you need to invoke your IIFE more than once. That involves giving it a name so that you can address it again, so technically it's not an IIFE anymore, but it works for the same reason that IIFEs do. I'm going to keep calling it

When you invoke a function, JavaScript creates a context for the variables and closures within it to live in. That context lives as long as anything outside the IIFE has a reference to anything inside it. That's why the traditional module pattern uses IIFEs: you can hide private data and functions within the IIFE's context.

But because you only invoke that function once, all of your module instances share the same context. You're storing your module options in the options variable, which is part of that share context instead of being part of the modules, so when you update the options in one of them, it updates the options in all of them. Sometimes, that's what you want, but not in your case.

What you want to do is create a new context for each module. This means you need to take your IIFE and keep a reference to it around, so that you can call it multiple times: in other words, it won't be an anonymous function anymore (or even necessarily an IIFE). But this is all doable. Here's one possible solution:

var moduleContext = function () {
    // default options
    var options = {
        first: "test",
        second: "test2",
        third: "test3"
    };
    // take in useroptions and replace default options
    var module = function(userOptions) {
        if (userOptions != null && userOptions != undefined
            && userOptions != 'undefined') {
            for (var opt in options) {
                if (userOptions.hasOwnProperty(opt)) {
                    options[ opt ] = userOptions[ opt ];
                }
            }
        }
    };

    //prototype
    module.prototype = {
        init: module,
        options: options
    };

    return module;

};

var my_module3 = new (moduleContext())();
my_module3.init({
    first: "Mike",
    second: "Lisa",
    third: "Mary"
});
var my_module2 = new (moduleContext())();
my_module2.init({
    first: "Barry",
    second: "Larry",
    third: "Sam"
});
console.log(my_module2.options, my_module3.options);

The magic happens in those two new (moduleContext())() lines. The moduleContext() function, like your IIFE, sets up a context for the module constructor function, and then returns it. The new operator then works on the function that gets returned, and calls it with no arguments (the last set of parens). The extra parens around the call to moduleContext() are needed for the same reason they're needed on an IIFE: they resolve some ambiguities in the JavaScript parser.

Now, your two modules get created in two different contexts. Because of that, you can set the "common" options object in either module (like you currently do), but only the options object in that module's context will be affected. The other one doesn't get touched, so you can set your options separately.

like image 22
The Spooniest Avatar answered Oct 27 '22 09:10

The Spooniest