RobinL
RobinL

Reputation: 11597

Best way to combine the module pattern with closures

I'm currently trying to implement some common JS concepts in little projects to understand better how to use them.

I've been working on a simple game, trying to understand and use the module pattern and closures. I'm using the module pattern from Stoyan Stefanov's 'patterns' book.

I'm struggling to understand how best to mix modules and closures.

I'd like to know if I'm organising the following code in a sensible way? If so, my question is: what's the best way to modify the code so that in the $(function(){}) I have access to the update() function?

MYAPP.utilities = (function() {

    return {
        fn1: function(lives) {
            //do stuff
        }
    }
})();

MYAPP.game = (function() {

    //dependencies
    utils = MYAPP.utilities

    return {
        startGame: function() {

            //initialisation code
            //game state, stored in closure
            var lives = 3;
            var victoryPoints = 0;

            function update(){
                utils.fn1(lives);
                //do other stuff
            }

        }
    }
})();

$(function(){
    MYAPP.game.startGame();

    //Want to do this, but it won't work 
    //because I don't have access to update

    $('#button').on('click',MYAPP.game.update) 

});

I've come up with a couple of options which would work, but I'd like to know if they're good practice, and what the best option is.

Options:

(1) Bind $('#button').on('click', ...) as part of the startGame initialisation code.

(2) Assign the update() function to a variable, and return this variable from the startGame function, So in $(function(){}) we could have updatefn = MYAPP.game.startGame(); and then $('#button').on('click',MYAPP.game.update)

(3)? Is there a better way?

Thank you very much for any help,

Robin

Upvotes: 4

Views: 1746

Answers (2)

Fabrício Matté
Fabrício Matté

Reputation: 70199

First off, to access the update function in that fashion it will have to exposed in the returned object.

return {
    update: function() {
        [...]
    },
    startGame: function() {
        [...]
        this.update();
    }
}

Calling obj.method() automatically sets the this reference inside this method call to obj. That is, calling MYAPP.game.startGame() sets this to MYAPP.game inside this startGame method call. More details about this behavior here.

You will also want to move the lives variable to a common scope which is accessible by both startGame and update methods, which is exactly what the closure is for:

MYAPP.game = (function() {

    [...]
    var lives; //private/privileged var inside the closure, only accessible by
               //the returned object's function properties

    return {
        update: function() {
            utils.fn1(lives);
        },
        startGame: function() {
            [...]
            lives = 3; //sets the closure scope's lives variable
            [...]
            this.update();
        }
    }
})();

Fiddle

In this case you will need some method to set the lives variable when you want to change it. Another way would be to make the lives variable public as well by making it a property of the returned object and accessing it through this.lives inside of the methods.

NOTE: If you simply pass a reference to the function object stored as property of the returned object as in:

$('#button').on('click', MYAPP.game.update);

The this reference inside the click handler will not point to MYAPP.game as the function reference that has been passed will be called directly from the jQuery core instead of as an object's member function call - in this case, this would point to the #button element as jQuery event handlers set the this reference to the element that triggered the handler, as you can see here.

To remedy that you can use Function.bind():

$('#button').on('click', MYAPP.game.update.bind(MYAPP.game));

Or the old function wrapper trick:

$('#button').on('click', function() {
    MYAPP.game.update(); //called as method of an obj, sets `this` to MYAPP.game
});

This is important when the this keyword is used inside the update method.

Upvotes: 4

ulentini
ulentini

Reputation: 2412

There are a few issues in your code. First, update() function is not visible outside the object your creating on the fly. To make it part of game object it has to be on the same level as startGame.

Also, if you declare var lives = 3 it will be a local variable and it won't be visible outside startGame() function, as well as victoryPoints. These two variable have to be visible in some way (via closure or as object fields).

Finally, attaching MYAPP.game.update as an event listener will attach just that function, preventing you from using all other object methods/functions. Depending on what you want to do you might prefer to pass a closure like function() { MYAPP.game.update() } instead.

Your code should look something like:

MYAPP.utilities = (function() {

    return {
        fn1: function(lives) {
            console.log(lives);
        }
    }
})();

MYAPP.game = (function() {

    //dependencies
    utils = MYAPP.utilities

    var lives;
    var victoryPoints;

    return {
        startGame: function() {

            //initialisation code
            //game state, stored in closure
            lives = 3;
            victoryPoints = 0;
        },

        update: function() {
            utils.fn1(lives);
            //do other stuff
        }
    }
})();

$(function(){
    MYAPP.game.startGame();

    //Want to do this, but it won't work 
    //because I don't have access to update

    $('#button').on('click', MYAPP.game.update) 

});

(DEMO on jsfiddle)

Upvotes: 1

Related Questions