【问题标题】:Passing 'this' and argument to addEventListener function without using bind在不使用绑定的情况下将“this”和参数传递给 addEventListener 函数
【发布时间】:2014-06-29 05:03:19
【问题描述】:

removeEventListener in bootstrapped addon not working when addon disabled之后,我正在探索其他可能性。

除了使用bind()和缓存绑定的函数,有没有办法使用'this'并传递参数?

// works fine but can't pass argeement
contextMenu.addEventListener('popupshowing', 
     this.contextPopupShowing, false);

// passes the argument but 'this' is no longer available
contextMenu.addEventListener('popupshowing', 
    function(){this.contextPopupShowing(window);}, false);

我一直在使用bind() 的许多事件侦听器,我正在寻找不使用bind() 的替代方法

我什至尝试使用来自<menupopup id="contentAreaContextMenu" ...> 的递归函数来获取window

更新:bind()干扰removeEventListener

【问题讨论】:

  • 使用.bind() 连接正确的this 有什么问题?这正是它的用途。还有其他解决方法,但没有一个像 .bind() 那样干净。
  • 谢谢 jfriend00。如前所述,bindremoveEventListener 的干扰
  • 您想要在函数中使用 .removeEventListener() 和控制 this ptr 的组合意味着您必须同时创建一个存根函数以传递给 .addEventListener() 并记住该存根函数以用于.removeEventListener()。没有其他选择。

标签: javascript firefox firefox-addon firefox-addon-restartless


【解决方案1】:

既然我们谈论的是无重启插件...很多无重启插件使用 unloadunloadWindow 辅助函数,以便更轻松地正确实现 shutdown 并帮助处理 @ 之类的东西987654326@,请耐心等待。

助手 - unload

首先,unload 是一个辅助函数,您可以将另一个函数传递给它,它将在shutdown 上运行(或者可以手动调用)。大多数实现都与此极为相似:

var unloaders = []; // Keeps track of unloader functions.

function unload(fn) {
  if (typeof(fn) != "function") {
    throw new Error("unloader is not a function");
  }
  unloaders.push(fn);
  return function() {
    try {
      fn();
    }
    catch (ex) {
      Cu.reportError("unloader threw " + fn.toSource());
      Cu.reportError(ex);
    }
    unloaders = unloaders.filter(function(c) { return c != fn; });
  };
}

然后你会联系shutdown 做正确的事:

function shutdown() {
  ...
  for (let i = unloaders.length - 1; i >= 0; --i) {
    try {
      unloaders[i]();
    }
    catch (ex) {
      Cu.reportError("unloader threw on shutdown " + fn.toSource());
      Cu.reportError(ex);
    }
  }
  unloaders.length = 0;
}

使用unload

现在您可以执行以下操作:

function startup() {
  setupSomething();
  unload(removeSomething);

  setupSomethingElse();
  var manualRemove = unload(removeSomethingElse);
  ...
  if (condition) {
    manualRemove();
  }
}

助手 - unloadWindow

您通常需要创建第二个函数unloadWindow 以在您的插件关闭或窗口关闭时卸载内容,无论首先发生什么。当窗口关闭时不删除东西可能非常棘手,并且很容易创建 Zombie compartmentsbootstrap.js 和/或代码模块(这是来自编写和审查无重启附加组件的经验)。

function unloadWindow(window, fn) {
  let handler = unload(function() {
    window.removeEventListener('unload', handler, false);
    try {
      fn();
    }
    catch (ex) {
      Cu.reportError("window unloader threw " + fn.toSource());
      Cu.reportError(ex);
    }
  });
  window.addEventListener('unload', handler, false);
};

(有些人可能想“优化”这个,因为只有一个 "unload" 处理程序,但通常你只有这么几个 unloadWindow 调用,这无关紧要。)

把它们放在一起

现在你可以.bind 做任何事情,让卸载闭包跟踪它。此外,您可以使用它来将关闭代码保留在初始化代码旁边,这可能会增加可读性。

function setupWindow(window, document) {
  var bound = this.contextPopupShowing.bind(this);
  contextMenu.addEventListener('popupshowing', bound, false);
  unloadWindow(window, function() {
    contextMenu.removeEventListener('popupshowing', bound, false);
  });

  // Or stuff like
  var element = document.createElement(...);
  contextMenu.appendChild(element);
  unloadWindow(window, function() {
    contextMenu.removeChild(element);
  });

  // Or just combine the above into a single unloader
  unloadWindow(window, function() {
    contextMenu.removeEventListener('popupshowing', bound, false);
    contextMenu.removeChild(element);
  });
}

【讨论】:

  • 谢谢尼尔斯。我了解绑定函数的缓存。我写了一个 tiny 无需重启的插件来熟悉自己(没有 pref,没有 XUL,简单的上下文菜单搜索),它工作正常。它有 4 个事件监听器。我可以绑定和缓存它们(创建 4 个新变量和 4 个新函数),这实际上是插件大小的两倍(在内存中)。在采用这种方法之前,我想确保没有其他(更好的)方法可以做到这一点。 :)
  • 是的,我不知道您在计划什么,但是除非您拥有大量这些并且在移动设备上,否则用于使用和保留绑定函数的额外内存通常可以忽略不计。但是如果你有很多这样的东西,无论如何你都会遇到性能问题。 ;) 例如我现在打开的单个 github 存储库页面消耗的内存量与我所有附加组件的总和差不多,其中包括 Adblock Plus 及其庞大的过滤器列表。
  • Gracious........我实际上是第一次在这里做绑定的新手:GitHub :: Noitidart / HiliteOnSelection。这是一个特殊的邮递员,谢谢。我不知道它们是真正的辅助函数。
  • 我不清楚卸载程序。我将把它放在一个单独的问题中。
  • 今天早上我太仓促了,因为我发布和简化了太多代码。最好以相反的顺序调用卸载程序。编辑以改变这一点。
【解决方案2】:

在支持bind() 之前,您必须在函数外部保存对this 的引用。然后传入一个可以按照你想要的方式转发调用的函数。

var self = this;
contextMenu.addEventListener('popupshowing', function() {
     self.contextPopupShowing.apply(self, arguments);
}, false);

在这种情况下,我们使用apply 将上下文设置为self,我们保存的this 版本,并将任何arguments 通过神奇的arguments 关键字传递给匿名函数包含调用函数时传递的参数列表。

【讨论】:

  • 谢谢亚历克斯。 apply(或call)是否会像bind一样干扰removeEventListener
  • 附言。有人在addEventListener using apply() 说:“它不会将 apply 和 call 函数作为属性”!?
  • 这不允许 OP 在事件处理程序上调用 removeEventListener(),这显然是一项要求。
【解决方案3】:

您不必将bind 用于addEventListener。您可以使用handleEvent。我也在那个主题中链接了你:

Removing event listener which was added with bind

MDN :: EventTarget.addEventListener - The value of "this" within the handler

handleEvent实际上是firefox代码库中javascript代码的常用方式。

直接从 MDN 复制:

var Something = function(element) {
  this.name = 'Something Good';
  this.handleEvent = function(event) {
    console.log(this.name); // 'Something Good', as this is the Something object
    switch(event.type) {
      case 'click':
        // some code here...
        break;
      case 'dblclick':
        // some code here...
        break;
    }
  };

  // Note that the listeners in this case are this, not this.handleEvent
  element.addEventListener('click', this, false);
  element.addEventListener('dblclick', this, false);

  // You can properly remove the listners
  element.removeEventListener('click', this, false);
  element.removeEventListener('dblclick', this, false);
}

我最常使用bind 的地方是在执行for 循环时,我使用arr[i] 之类的数组中的某些内容创建匿名函数。如果我不绑定它,那么它总是只取数组的最后一个元素,我不知道为什么,我讨厌它,所以我去使用[].forEach.call(arr, function(arrI)

【讨论】:

  • 我读过,但我不清楚。该元素在window 中,因此这是否意味着handleEvent 监听window.addEventListener('click', this, false); 中的所有窗口事件?还有其他事件侦听器需要转到其他地方。
  • 不,但是任何el.addEventListener('click', this, false) 的行为都会通过它进行路由。其中thiswindow 范围。但是为什么要附加窗口人,尝试使用 JSM 模块。 Bootstrapped 插件是它自己的一个范围,所以只要把你的东西放在那里。
  • 我猜你也可以使用el.addEventListener('click', window, false) 注意window 代替this
  • 谢谢诺伊达特。我正在尝试handleEvent 来捕捉所有事件并相应地重定向。
【解决方案4】:

http://2ality.com/2013/06/auto-binding.html

var listener = myWidget.handleClick.bind(myWidget);
domElement.addEventListener('click', listener);
...
domElement.removeEventListener(listener);

【讨论】:

    猜你喜欢
    • 2012-11-30
    • 2014-05-25
    • 2019-07-13
    • 2020-09-10
    • 1970-01-01
    • 2014-06-26
    • 1970-01-01
    • 2012-08-15
    • 2012-09-04
    相关资源
    最近更新 更多