Reputation: 11597
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
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();
}
}
})();
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
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)
});
Upvotes: 1