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"
    });
5
  • 1
    Why does your code modify the default options? You need to create a new object for each instance! Commented Oct 17, 2014 at 15:01
  • stackoverflow.com/questions/6504726/… Commented Oct 17, 2014 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? Commented Oct 17, 2014 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. Commented Oct 17, 2014 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: stackoverflow.com/a/16063711/1641941 Commented Oct 17, 2014 at 23:53

2 Answers 2

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" 
Sign up to request clarification or add additional context in comments.

3 Comments

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?
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 Yes, it's less confusing to describe them as class and instance, even though it's not really a class in JavaScript
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.

3 Comments

What is the point of using prototype if you're going to change it every time you create an instance? stackoverflow.com/a/16063711/1641941
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.
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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.