问题
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 = thisbecause it's fast and easy.But
var self = thisdoes 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
keydownevent 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 thekeydownevent 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 towindowunless you bind it to another object viabindorapplywhen 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()usingfunctionsand.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
thisvariable. - The inner function is a closure, hence it has reference to self.
- When the closure function is called:
thispoints to the dom object, whileselfpoints to keyboard object. - The closure is called with
eventas 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