Javascript 范围 addEventListener 和 this
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/13996263/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Javascript scope addEventListener and this
提问by Raf
I am a C# developer experimenting with JavaScript and I'm trying to get my head around the scope :)
我是一名 C# 开发人员,正在试验 JavaScript,我正在努力了解范围:)
I have the following code which contains an addEventListenerin which I want to use a field from my object:
我有以下代码,其中包含addEventListener我想在其中使用对象中的字段的代码:
(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
我想在我的hander 中使用keys 数组,但我知道我无法访问它,因为这是该上下文中的窗口(对吗?)。如果我把它改成
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!
谢谢你的帮助!
回答by Benny
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 objectentirely 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.
大多数人会建议类似的东西,
var self = this因为它既快速又简单。但是
var self = this并没有将视图对象与视图逻辑完全分开,视图逻辑来自更正式的 C# 背景并查看您的代码,听起来像是您想做的事情。为了让回调仅在事件触发时执行,请将处理程序包装在一个函数中,以便立即对其进行评估,但仅在
keydown事件触发时执行(请参阅下面的代码)。理解 JS 中的作用域:无论执行上下文是什么,也是当前作用域。您的侦听器已添加到方法(称为
listen) on 中Keyboard.prototype,但keydown实际上触发了该事件window——处理程序在与定义位置不同的上下文中执行;它是在调用它的上下文中执行的,在这种情况下,window它的范围是 ,window除非您通过bind或apply在定义它时将其绑定到另一个对象。
In your code, windowis the view a user's interacting with, and Keyboardis 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 = thislike so many do, the view would be managing itself -- but only for that specific handler for keydownevents. This is inconsistent and would become hard to manage in a large project.
在您的代码中,window是用户与之交互的视图,并且Keyboard是该视图的控制器。在 MVC 模式中,就像您在 C#/.NET 中可能习惯的那样,当事情发生时,视图不会告诉自己该做什么,控制器会告诉视图该做什么。因此,如果您要var self = this像许多人一样通过使用来分配对控制器的引用,则视图将自行管理——但仅适用于keydown事件的特定处理程序。这是不一致的,并且在大型项目中会变得难以管理。
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 classis ready):
最佳解决方案(直到 ES6class准备就绪):
// 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():
- 注意:
.bind()兼容 ECMAScript 5+;如果你需要对旧版浏览器的解决方案,Mozilla已经发布到一个伟大的选择,.bind()使用functions和.call():
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind
Edit:Here's what your instantiated keyboardobject will look like using this new, modular solution: 
编辑:这是keyboard使用这个新的模块化解决方案实例化对象的样子:
回答by closure
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.
- 我们正在创建变量 self,它存储对
this变量的引用。 - 内部函数是一个闭包,因此它引用了 self。
- 调用闭包函数时:
this指向dom对象,whileself指向键盘对象。 - 闭包
event作为参数被调用,我们传递给键盘对象的成员函数。
回答by Musa
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();

