正确的绑定方式“;这个“;在JavaScript事件回调中

Correct way to bind "this" in JavaScript event callbacks?

本文关键字:JavaScript 事件 回调 这个 绑定 方式      更新时间:2023-09-26

我创建了一个名为SearchBox的类来处理搜索交互(延迟触发、按回车键进行搜索、在活动时阻止搜索、在搜索完成且文本发生更改时同步结果等)

所有的类方法都是原型方法,可以通过this访问。在下面的代码中,假设p是类的原型。

p.registerListeners = function () {
    $(this.element).on('keypress', this.searchKeyPressed);
};
p.unregisterListeners = function () {
    $(this.element).off('keypress', this.searchKeyPressed);
};

这不起作用,因为当按键事件调用searchKeyPressed处理程序时,它在this的上下文中不会这样做。我能想到的唯一解决方案是只有现代浏览器支持的解决方案,即将回调绑定到this,这实际上创建了一个新函数。由于它创建了一个新函数,我必须缓存它,以便稍后删除它,因为我必须传递与传递给on相同的对off的引用。

有比这更好的方法吗?或者这可以吗?

var boundKeyPressed;
p.registerListeners = function () {
    boundKeyPressed = this.searchKeyPressed.bind(this);
    $(this.element).on('keypress', boundKeyPressed);
};
p.unregisterListeners = function () {
    $(this.element).off('keypress', boundKeyPressed);
};

我认为jQuery.on可能会提供一种自动进行事件绑定的方法,但它似乎会根据调用方式将this绑定到不同的东西。例如,使用on('eventname',instance.func)时,this是"currentTarget"(不一定是冒泡术语中的"target"),而使用on('eventname','selector',instance.func)时,this是指与选择器匹配的元素。在任何一种情况下,func都会像与instance没有关系一样运行。

如果向事件添加命名空间,则可以绑定事件并轻松地一次解除所有事件的绑定。

绑定:

$(this.element).on('keypress.mynamespace', this.searchKeyPressed.bind(this));

解除绑定:

$(this.element).off('.mynamespace');

First,除非您希望您的页面使用寿命很长(或者您正在听带有数百种内容的按键,这是一个非常大的架构问题),否则这不会成为内存问题,即使手机有"键"。

第二.bind对所有不到五年历史的浏览器都有很好的支持,而且polyfill非常简单。

第三:你是100%正确的,必须缓存函数以便稍后处理它是不酷的,所以让我们对此做点什么

addEventListener(和attachEvent)有一个鲜为人知的技巧,它很乐意支持对象,并在对象上使用handleEvent方法。

我并不是什么都用它,因为有时它真的不值得,但对于游戏制作,我用它来输入,有点像:

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);
  }
}

我可以:

var gameplayEvents = ["keyup", "keydown"];
var keyboard = new Keyboard();
keyboard.listen(canvas, gameplayEvents);
// ongameover
keyboard.ignore(canvas, gameplayEvents);

如果你注意到的话,这都是100%纯JS。没有jQuery、extJS等。实际上,它也没有太多的代码。如果我只需要一个实例来处理mouseup和mousedown,我就可以将它变成一个对象文字;实际上,我所需要的只是一个带有handleEvent的对象,以便在handleEvent回调中成为this

只有一个例子值得担心。如果需要注销,我不会缓存任何额外的内容。

jQuery(和其他人)实际上在内部使用了这一点,以优化他们通常被滥用来生成的糟糕代码。

是的,也许我用ES6作弊。。。但这根本没有必要。

它比我过去做的事情更令人愉快:

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 }
);

同样,很多时候绑定是100%有效的
现在有一个关于绑定的ES7版本的建议,每次调用它时(如果通过这种方式),它都可能产生相同的值。

此外,语法还允许将各种很棒的东西链接在一起。

您可以不使用bind,而是使用jQuery.proxy函数来保留上下文,这将创建一个包装函数,您可以取消绑定:

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));
};

在构造函数中添加bind(this),这样就不会在每次调用.bind时都创建新函数。bind在每次调用它时都会创建新函数,因此如果使用以下内容附加它:$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),因为处理程序是相同的。

使用此方法,实质上就是覆盖该实例的函数。它将不再调用原型上的类函数,而是调用该指令上的类功能。

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);
}

您可以使用闭包。

p.registerListeners = function() {
    var me = this;
    $(me.element).on('keypress', function() {
        me.searchKeyPressed.apply(me, arguments);
    });
};

使用apply传入参数。