6

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"
    });
AntonB
  • 2,724
  • 1
  • 31
  • 39
  • 1
    Why does your code modify the default options? You need to create a new object for each instance! – Bergi Oct 17 '14 at 15:01
  • http://stackoverflow.com/questions/6504726/how-can-i-pass-parameters-to-a-module-pattern-to-override-default-values-privat – AntonB Oct 17 '14 at 15:02
  • Well, there you asked specifically to override the default values that are shared amongst your instances. Isn't that what you wanted? – Bergi Oct 17 '14 at 15:04
  • Can you provide a solution then? I have provided my full code, obviously I am doing something wrong... I want each object to carry their own options. – AntonB Oct 17 '14 at 15:05
  • If you're going the prototype way please be careful to specify instance members on the prototype especially if they are mutable (example uses strings so it's save). You can read more about it here: http://stackoverflow.com/a/16063711/1641941 – HMR Oct 17 '14 at 23:53

2 Answers2

6

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" 
www0z0k
  • 4,444
  • 3
  • 27
  • 32
  • I'm not sure what you mean by the "properties of a function" (where does OP use them?); and how "properties of `function prototype` are different across instances" (that's just contrary to what they should be). Would you care to elaborate, please? – Bergi Oct 18 '14 at 14:53
  • there is no such thing as `class` in an OOP meaning in JS, that's why I use a word `function` to describe an `object` created by calling `new test()` in my example, it might be better to say `class` and `class instance`. And properties (class members) defined in `prototype` being accessed directly (`t1.first`, not `t1.prototype.first` in my example) behave like `class instance` properties, not like `static` `class` fields – www0z0k Oct 20 '14 at 07:02
  • @www0z0k Yes, it's less confusing to describe them as class and instance, even though it's not really a class in JavaScript – Ruan Mendes Oct 20 '14 at 12:51
5

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.

The Spooniest
  • 2,863
  • 14
  • 14
  • What is the point of using prototype if you're going to change it every time you create an instance? http://stackoverflow.com/a/16063711/1641941 – HMR Oct 20 '14 at 23:54
  • I admit that the solution I posted is not entirely idiomatic or practical. The accepted answer is better from those standpoints. I posted this answer for two reasons: because it more closely matches the code given in the question, and because it more clearly illustrates why that code doesn't do what the person who asked it wants to do. – The Spooniest Oct 21 '14 at 13:04
  • This makes sense, the IIFE is being invoked once within the module so any "new" instances of the module have already been invoked with the last modules options overwriting the users options in any "new" instances. – AntonB Oct 21 '14 at 13:52