I am a C# developer experimenting with JavaScript and I'm trying to get my head around the scope :)
I have the following code which contains an addEventListener
in which I want to use a field from my object:
(function(window) {
function Keyboard() {
this.keys = {};
}
Keyboard.prototype.handle_keydown = function(args) {
this.keys[args.keyCode] = true;
}
Keyboard.prototype.listen = function() {
window.addEventListener('keydown', this.handle_keydown);
}
app.util.keyboard = new Keyboard();
})(window);
I would like to use the keys array in my hander, but understand that I cannot access is by using this, because this is the window in that context (correct?). If I change it to
app.util.keyboard.keys[args.keyCode] = true;
it works, but I'm not sure that's a good way to fix it.
I found this question, which seems rather similar, but Im not sure how I can fit it into my example.
Thanks for your help!
A few things:
Most people will suggest something like
var self = this
because it's fast and easy.But
var self = this
does not separate the view object entirely from the view logic, which coming from a more formal C# background and looking at your code, sounds like something you want to do.In order to have the callback execute only when the event fires, wrap the handler in a function, so that it's evaluated right away, but only executed when and if a
keydown
event fires (see the code below).Understanding scope in JS: Whatever the execution context is, is also the current scope. Your listener was added in a method (called
listen
) onKeyboard.prototype
, but thekeydown
event is actually fired onwindow
-- the handler is executing in a different context than where it was defined; it's executing within the context of what is invoking it, in this case,window
, so it's scoped towindow
unless you bind it to another object viabind
orapply
when it's defined.In your code,
window
is the view a user's interacting with, andKeyboard
is that view's controller. In MVC patterns like what you're probably used to in C#/.NET, views don't tell themselves what to do when things happen, controllers tell views what to do. So, if you were to assign a reference to the controller by usingvar self = this
like so many do, the view would be managing itself -- but only for that specific handler forkeydown
events. This is inconsistent and would become hard to manage in a large project.A solution:
A better solution:
The best solution (until ES6
class
is ready):.bind()
is compatible with ECMAScript 5+; if you need a solution for older browsers, Mozilla has posted a great alternative to.bind()
usingfunctions
and.call()
:https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind
Edit: Here's what your instantiated
keyboard
object will look like using this new, modular solution:How this code works:
this
variable.this
points to the dom object, whileself
points to keyboard object.event
as a parameter that we pass on to the member function of the keyboard object.How about