在 JavaScript 事件回调中绑定“this”的正确方法?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/34032028/
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
Correct way to bind "this" in JavaScript event callbacks?
提问by Triynko
I created a class called SearchBox to handle search interaction (delayed trigger, search on enter key press, preventing searches while one is active, synchronizing results when a search completes and the text has changed, etc.).
我创建了一个名为 SearchBox 的类来处理搜索交互(延迟触发器、按 Enter 键时搜索、在搜索时阻止搜索、在搜索完成且文本已更改时同步结果等)。
All the class methods are prototype methods, meant to be accessed via this
. In the following code, assume p
is the class's prototype.
所有类方法都是原型方法,旨在通过this
. 在下面的代码中,假设p
是类的原型。
p.registerListeners = function () {
$(this.element).on('keypress', this.searchKeyPressed);
};
p.unregisterListeners = function () {
$(this.element).off('keypress', this.searchKeyPressed);
};
That doesn't work, because when the keypress event calls the searchKeyPressed
handler, it doesn't do so in the context of this
. The only solution I can come up with is one that only modern browsers support, which is to bind the callback to this
, which actually creates a new function. Since it creates a new function, I have to cache it in order to remove it later, since I have to pass the same reference to off
that I passed to on
.
这不起作用,因为当 keypress 事件调用searchKeyPressed
处理程序时,它不会在this
. 我能想到的唯一解决方案是只有现代浏览器支持的解决方案,即将回调绑定到this
,这实际上创建了一个新函数。因为它创建了一个新函数,所以我必须缓存它以便稍后删除它,因为我必须将相同的引用off
传递给我传递给on
.
Is there a better way to do it than this, or is this ok?
有没有比这更好的方法,或者这可以吗?
var boundKeyPressed;
p.registerListeners = function () {
boundKeyPressed = this.searchKeyPressed.bind(this);
$(this.element).on('keypress', boundKeyPressed);
};
p.unregisterListeners = function () {
$(this.element).off('keypress', boundKeyPressed);
};
I thought that maybe jQuery.on
would provide a way to do this event binding automatically, but instead it seems like it binds this
to different things depending on how it's called. For example, when using on('eventname',instance.func)
, this
is the "currentTarget" (not necessarily "target" in bubbling terms), whereas when using on('eventname','selector',instance.func)
, this
refers to the element matching the selector. In either case, the func
runs as though it has no relationship with instance
.
我认为这可能jQuery.on
会提供一种自动执行此事件绑定的方法,但它似乎this
根据调用方式绑定到不同的事物。例如,当使用时on('eventname',instance.func)
,this
是“currentTarget”(在冒泡术语中不一定是“目标”),而当使用时on('eventname','selector',instance.func)
,this
是指与选择器匹配的元素。在任何一种情况下,func
运行都好像它与instance
.
回答by Koen.
If you add a namespace to your events you can bind events and easily unbind them all at once.
如果为事件添加命名空间,则可以绑定事件并轻松地一次性解除所有事件的绑定。
To bind:
绑定:
$(this.element).on('keypress.mynamespace', this.searchKeyPressed.bind(this));
To unbind:
解除绑定:
$(this.element).off('.mynamespace');
回答by Norguard
First, unless you expect your page to be very long lived (or you're listening to keypresses with hundreds of things, which is a very big architecture problem), this isn't going to be much of an issue, where memory is concerned, even if phones had "keys".
首先,除非你希望你的页面很长(或者你正在听数百件事的按键,这是一个非常大的架构问题),否则这不会是一个很大的问题,在内存方面,即使手机有“钥匙”。
Second, .bind
has great support for all browsers less than half a decade old, and is dirt simple to polyfill.
其次,.bind
对所有不到五年历史的浏览器都有很好的支持,并且易于填充。
Third: you're 100% right, that it's not cool to have to cache the function to be able to deal with it later, so let's do something about that.
第三:您 100% 正确,必须缓存函数以便稍后处理它并不酷,所以让我们对此做点什么。
There's a little known trick to addEventListener
(and attachEvent
), in that it happily supports objects, with handleEvent
methods on them.
addEventListener
(和attachEvent
)有一个鲜为人知的技巧,因为它很高兴地支持对象,并带有handleEvent
方法。
I don't use this for everything, as sometimes it's really just not worth it, but for game-making, I've used it for inputs, kind of like so:
我不会把它用于所有事情,因为有时它真的不值得,但对于游戏制作,我已经将它用于输入,有点像这样:
class Input {
constructor (events) {
this.events = events || [];
}
handleEvent (e) {
var input = this;
var method = e.type;
if (typeof input[method] === "function") {
input.dispatchEvent(method, e);
}
}
dispatchEvent (method, content) {
var input = this;
input[method](content);
}
listen (el, events) {
var input = this;
events = events || input.events;
events.forEach(event => el.addEventListener(event, input));
return this;
}
ignore (el, events) {
var input = this;
events = events || input.events;
events.forEach(event => el.removeEventListener(event, input));
return this;
}
}
class Keyboard extends Input {
constructor () {
super(["keydown", "keyup"]);
var keyboard = this;
keyboard.keys = new Set();
}
press (key) { this.keys.add(key); }
release (key) { this.keys.delete(key); }
isPressed (key) { return this.keys.has(key); }
keydown (e) {
var key = e.keyCode;
this.press(key);
}
keyup (e) {
var key = e.keyCode;
this.release(key);
}
}
I could then:
然后我可以:
var gameplayEvents = ["keyup", "keydown"];
var keyboard = new Keyboard();
keyboard.listen(canvas, gameplayEvents);
// ongameover
keyboard.ignore(canvas, gameplayEvents);
And if you'll note, it's all 100% pure JS.
No jQuery, extJS, etc.
And really, it's seriously not a lot more code, either.
I could make it one object-literal, if I just needed one instance to handle mouseup and mousedown; really all I need is an object with a handleEvent, to become this
inside of the handleEvent callback.
如果你注意到,它都是 100% 纯 JS。没有 jQuery、extJS 等。实际上,它也没有更多的代码。如果我只需要一个实例来处理 mouseup 和 mousedown,我可以把它变成一个对象字面量;我真正需要的只是一个带有 handleEvent 的对象,以成为this
handleEvent 回调的内部。
There's only one instance to worry about. I don't cache anything extra, if I need to unregister.
只有一种情况需要担心。如果我需要注销,我不会缓存任何额外的东西。
jQuery (and others) actually use this internally, to optimize the atrocious code which they're typically abused into producing.
jQuery(和其他人)实际上在内部使用它来优化他们通常被滥用的糟糕代码。
Yes, perhaps I'm cheating by using ES6... ...but it's not necessary at all.
是的,也许我在使用 ES6 作弊... ...但这根本没有必要。
It's just more cheerful than what I used to do:
它只是比我以前做的更开朗:
function Parent (args) { }
extend(Parent.prototype, { /*public-methods*/ });
function Child (args) {
Parent.call(this);
// construct
}
extend(
Child.prototype,
Parent.prototype,
{ /*public-override-methods*/ },
{ constructor: Child }
);
And again, there are lots of times when bind is 100% valid.
There's a proposal right now, for an ES7 version of bind, which would potentially produce the same value, every time it's called (if it goes through that way).
而且,很多时候绑定是 100% 有效的。
现在有一个提议,对于 ES7 版本的 bind,它可能会在每次调用时产生相同的值(如果它通过这种方式)。
With the added benefit that the syntax allows for chaining all kinds of awesome things together, as well.
额外的好处是语法允许将各种很棒的东西链接在一起。
回答by Koen.
Instead of using bind
, you could use the jQuery.proxy
functionto preserve context, which creates a wrapper function you canunbind:
bind
您可以使用该jQuery.proxy
函数来保留上下文,而不是使用,这会创建一个可以解除绑定的包装函数:
jQuery.proxy
has multiple variants, but for now the jQuery.proxy(function, context)
is what you'll have to use:
jQuery.proxy
有多种变体,但现在jQuery.proxy(function, context)
您必须使用:
p.registerListeners = function () {
$(this.element).on('keypress', $.proxy(this.searchKeyPressed, this));
};
p.unregisterListeners = function () {
$(this.element).off('keypress', $.proxy(this.searchKeyPressed, this));
};
回答by Wish
Add bind(this)
in the constructor - so you dont create new function every time you call .bind
. bind
creates new function every time you call it, so if you attach it with this: $el.on("click", this.handler.bind(this))
, you cannot detach it with $el.off("click", this.handler.bind(this))
because the handler is not the same. (this.handler.bind(this) !== this.handler.bind(this)
) If you save that reference to that binded function (like in the constructor this.handler = this.handler.bind(this)
), then you can $el.on("click", this.handler)
and $el.off("click", this.handler)
, because the handler is the same.
添加bind(this)
构造函数 - 这样您就不会每次调用.bind
. bind
每次调用时都会创建新函数,因此如果使用 this: 附加它$el.on("click", this.handler.bind(this))
,则无法将其分离,$el.off("click", this.handler.bind(this))
因为处理程序不相同。( this.handler.bind(this) !== this.handler.bind(this)
) 如果您保存对该绑定函数的引用(如在构造函数中this.handler = this.handler.bind(this)
),那么您可以$el.on("click", this.handler)
和$el.off("click", this.handler)
,因为处理程序是相同的。
With this method, you are essentially overwriting that function for that instance. It will no longer call function like on prototype, but function like on that instnce.
使用此方法,您实际上是为该实例覆盖该函数。它不再像原型上那样调用函数,而是像在那个实例上那样调用函数。
function MyObject($el) {
this.testValue = "I am really this!";
this.$el = $el;
this.onClick = this.onClick.bind(this);
this.render();
}
MyObject.prototype.onClick = function(evt) {
console.log(this.testValue); // logs "I am really this!"
}
MyObject.prototype.render = function() {
var $a = $("<a>", {"text": "Click on me!"}).appendTo($el.empty());
$a.on("click", this.onClick);
}
回答by Comptonburger
You can use a closure.
您可以使用闭包。
p.registerListeners = function() {
var me = this;
$(me.element).on('keypress', function() {
me.searchKeyPressed.apply(me, arguments);
});
};
Use apply to pass in the arguments.
使用 apply 传入参数。