问题
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!
回答1:
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, and Keyboard
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 using var self = this
like so many do, the view would be managing itself -- but only for that specific handler for keydown
events. This is inconsistent and would become hard to manage in a large project.
A solution:
Keyboard.prototype.listen = function() {
window.addEventListener('keydown', function(e) {
this.handle_keydown(e);
}.bind(this), false);
}
A better solution:
Keyboard.prototype.view = window;
Keyboard.prototype.listen = function() {
this.view.addEventListener('keydown', function(e) {
this.handle_keydown(e);
}.bind(this), false);
}
The best solution (until ES6 class
is ready):
// define
function addViewController(view) {
function ViewController() {
this.handle_keydown = function(args) {
// handle keydown events
};
this.listen = function() {
this.view.addEventListener('keydown', function(e) {
this.handle_keydown(e);
}.bind(this), false);
};
this.view = view;
return this;
}
return new ViewController(view);
}
// implement
var keyboard = addViewController(window);
keyboard.listen();
- Note:
.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:

回答2:
Keyboard.prototype.listen = function() {
var self = this;
window.addEventListener('keydown', function(event) {
self.handle_keydown(event);
// self is your Keyboard object. You can refer to all your properties from this
});
}
How this code works:
- We are creating variable self, which stores reference to
this
variable. - The inner function is a closure, hence it has reference to self.
- When the closure function is called:
this
points to the dom object, whileself
points to keyboard object. - The closure is called with
event
as a parameter that we pass on to the member function of the keyboard object.
回答3:
How about
function Keyboard() {
this.keys = {};
var self = this;
this.handle_keydown = function(args) {
self.keys[args.keyCode] = true;
}
this.listen = function() {
window.addEventListener('keydown', this.handle_keydown);
}
}
app.util.keyboard = new Keyboard();
来源:https://stackoverflow.com/questions/13996263/javascript-scope-addeventlistener-and-this