【问题标题】:removeEventListener on anonymous functions in JavaScriptJavaScript 中匿名函数的 removeEventListener
【发布时间】:2011-06-24 10:06:34
【问题描述】:

我有一个包含方法的对象。这些方法被放入匿名函数内的对象中。它看起来像这样:

var t = {};
window.document.addEventListener("keydown", function(e) {
    t.scroll = function(x, y) {
        window.scrollBy(x, y);
    };
    t.scrollTo = function(x, y) {
        window.scrollTo(x, y);
    };
});  

(还有很多代码,但这足以说明问题)

现在我想在某些情况下停止事件监听器。因此我试图做一个 removeEventListener 但我不知道如何做到这一点。我在其他问题中读到无法在匿名函数上调用 removeEventListener,但在这种情况下也是如此吗?

我在匿名函数内部创建了一个方法,因此我认为这是可能的。看起来像这样:

t.disable = function() {
    window.document.removeEventListener("keydown", this, false);
}

为什么我不能这样做?

还有其他(好的)方法可以做到这一点吗?

奖金信息;这只能在 Safari 中工作,因此缺少 IE 支持。

【问题讨论】:

  • 为什么不保存这个函数呢?事件处理程序可能不是匿名函数。
  • 我意识到这有点晚了,但您也可以使用 Node.setUserData/Node.getUserData 方法来存储有关元素的数据。例如,当你需要设置一个匿名监听器(并且能够移除它)时,首先将 userdata 设置为一个匿名函数(Elem.setUserData('eventListener', function(e){console.log('Event fired.');}, null); 然后执行 Elem.addEventListener('event', Elem.getUserData('eventListener'),错误的); ...对于 removeEventListener 也是如此。希望你能看到这个。
  • 编辑:根据之前的评论,我猜这只能在 Firefox 中使用......我刚刚尝试过 IE8(IE9 未知)、Safari 5.1.2、Chrome (?)、Opera 11..没有骰子
  • @Heretic Monkey 这个链接是无关紧要的:1)它有没有'keydown'事件和2)所有的答案都包括鼠标点击

标签: javascript safari dom-events anonymous-function


【解决方案1】:

除了 Safari 之外,大多数流行浏览器的最新版本都支持这种新方法。

检查caniuse 以获得更新的支持。

我们可以向addEventListner 添加一个名为signal 的选项,并从AbortController 中分配一个signal,您以后可以在其上调用abort() 方法。

这是一个例子。

我们创建一个AbortController

const controller = new AbortController();

然后我们创建eventListner 并传入选项signal

document.addEventListener('scroll',()=>{
    // do something
},{signal: controller.signal})

然后在稍后删除eventListner,我们调用:

controller.abort()

【讨论】:

    【解决方案2】:

    我刚刚遇到了与版权保护 wordpress 插件类似的问题。代码是:

    function disableSelection(target){
     if (typeof target.onselectstart!="undefined") //For IE 
      target.onselectstart=function(){return false}
     else if (typeof target.style.MozUserSelect!="undefined") //For Firefox
      target.style.MozUserSelect="none"
     else //All other route (For Opera)
      target.onmousedown=function(){return false}
    target.style.cursor = "default"
    }
    

    然后它是由松散地发起的

    <script type="text/javascript">disableSelection(document.body)</script>.
    

    我只是通过将其他匿名函数附加到此事件来解决这个问题:

    document.body.onselectstart = function() { return true; };
    

    【讨论】:

      【解决方案3】:

      在现代浏览器中,您可以执行以下操作...

      button.addEventListener( 'click', () => {
          alert( 'only once!' );
      }, { once: true } );
      

      https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Parameters

      【讨论】:

      • 冷静,直到你发现没有任何版本的 IE 和 edge
      • 有了这个用于 DOM Level 4 条目的 polyfill 你应该没问题npmjs.com/package/dom4
      【解决方案4】:

      我知道这是一个相当老的帖子,但我想我可能会为那些觉得它有用的人付出两分钱。

      脚本(对没有创意的方法名称表示歉意):

      window.Listener = {
          _Active: [],
          remove: function(attached, on, callback, capture){
              for(var i = 0; i < this._Active.length; i++){
                  var current = this._Active[i];
                  if(current[0] === attached && current[1] === on && current[2] === callback){
                      attached.removeEventListener(on, callback, (capture || false));
                      return this._Active.splice(i, 1);
                  }
              }
          }, removeAtIndex(i){
              if(this._Active[i]){
                  var remove = this._Active[i];
                  var attached = remove[0], on = remove[1], callback = remove[2];
                  attached.removeEventListener(on, callback, false);
                  return this._Active.splice(i, 1);
              }
          }, purge: function(){
              for(var i = 0; i < this._Active.length; i++){
                  var current = this._Active[i];
                  current[0].removeEventListener(current[1], current[2]);
                  this._Active.splice(i, 1);
              }
          }, declare: function(attached, on, callback, capture){
              attached.addEventListener(on, callback, (capture || false));
              if(this._Active.push([attached, on, callback])){
                  return this._Active.length - 1;
              }
          }
      };
      

      你可以像这样使用它:

      // declare a new onclick listener attached to the document
      var clickListener = Listener.declare(document, "click" function(e){
          // on click, remove the listener and log the clicked element
          console.log(e.target);
          Listener.removeAtIndex(clickListener);
      });
      
      // completely remove all active listeners 
      // (at least, ones declared via the Listener object)
      Listener.purge();
      
      // works exactly like removeEventListener
      Listener.remove(element, on, callback);
      

      【讨论】:

        【解决方案5】:
        window.document.removeEventListener("keydown", getEventListeners(window.document.keydown[0].listener));  
        

        可能是几个匿名函数,keydown1

        警告:仅适用于Chrome Dev Tools & 不能在代码中使用link

        【讨论】:

        • 谢谢,你解开了一个谜,至少在 Chrome 中是这样,因为许多小丑说是不可能的。伙计,你就像……蝙蝠侠!
        • getEventListeners 似乎是 Chrome Dev-tools 的一部分,因此除了调试之外并不能真正用于任何其他用途。
        • 刚试了一下,确认它只在 Devtools 中可用,在页面内的脚本中不可用。
        【解决方案6】:

        我偶然发现了同样的问题,这是我能得到的最佳解决方案:

        /*Adding the event listener (the 'mousemove' event, in this specific case)*/
        element.onmousemove = function(event) {
            /*do your stuff*/
        };
        /*Removing the event listener*/
        element.onmousemove = null;
        

        请记住,我只测试了 window 元素和 'mousemove' 事件,因此这种方法可能存在一些问题。

        【讨论】:

          【解决方案7】:

          就您的要求而言,这可能不是最佳解决方案。我还没有确定一种有效的方法来删除与事件侦听器调用内联声明的匿名函数。

          我个人使用一个变量来存储&lt;target&gt;,并在事件侦听器调用之外声明该函数,例如:

          const target = document.querySelector('&lt;identifier&gt;');

          function myFunc(event) { function code; }

          target.addEventListener('click', myFunc);

          然后移除监听器:

          target.removeEventListener('click', myFunc);

          不是您将收到的最佳建议,但要删除匿名函数,我发现唯一有用的解决方案是删除然后替换 HTML 元素。我敢肯定一定有更好的香草JS方法,但我还没有看到。

          【讨论】:

            【解决方案8】:

            如果你在实际的函数中,你可以使用 arguments.callee 作为函数的引用。如:

            button.addEventListener('click', function() {
                  ///this will execute only once
                  alert('only once!');
                  this.removeEventListener('click', arguments.callee);
            });
            

            编辑: 如果您在严格模式下工作,这将不起作用 ("use strict";)

            【讨论】:

            • 这很好,因为它保留了匿名函数的优点(不污染命名空间等)。
            • 在 WinJS 应用程序中尝试过,得到下一个错误:“在严格模式下不允许访问参数对象的 'callee' 属性”
            • @ValentinKantor:那是因为代码中有“使用严格”;声明,你不能在严格模式下使用被调用者。
            • 给内联函数一个名字,你可以引用它而无需诉诸arguments.callee:button.addEventListener('click', function handler() { this.removeEventListener('click', handler); });
            • 如 Mozilla 中所述:“警告:ECMAScript (ES5) 第 5 版禁止在严格模式下使用 arguments.callee()。避免使用 arguments.callee(),方法是给函数表达式命名或者在函数必须调用自身的地方使用函数声明。”
            【解决方案9】:

            Otto Nascarella 在严格模式下工作的解决方案的一个版本是:

            button.addEventListener('click', function handler() {
                  ///this will execute only once
                  alert('only once!');
                  this.removeEventListener('click', handler);
            });
            

            【讨论】:

            • 漂亮的解决方案!
            • 这可能不是正确的方法,但这是最简单的方法。
            • 这在 VueJS 指令的上下文中起作用。我创建了一个方法,我只希望它运行一次。
            • 我很好奇为什么这可能不是正确的方式
            • 非常好。这可能是公认的答案。
            【解决方案10】:

            为此提供更新的方法:

            //one-time fire
            element.addEventListener('mousedown', {
              handleEvent: function (evt) {
                element.removeEventListener(evt.type, this, false);
              }
            }, false);
            

            【讨论】:

            • 解释一下就好了。
            【解决方案11】:

            JavaScript: addEventListener 方法在它被调用的 EventTarget(Element|document|Window) 上注册指定的监听器。

            EventTarget.addEventListener(event_type, handler_function, Bubbling|Capturing);

            鼠标、键盘events WebConsole 中的示例测试:

            var keyboard = function(e) {
                console.log('Key_Down Code : ' + e.keyCode);
            };
            var mouseSimple = function(e) {
                var element = e.srcElement || e.target;
                var tagName = element.tagName || element.relatedTarget;
                console.log('Mouse Over TagName : ' + tagName);    
            };
            var  mouseComplex = function(e) {
                console.log('Mouse Click Code : ' + e.button);
            } 
            
            window.document.addEventListener('keydown',   keyboard,      false);
            window.document.addEventListener('mouseover', mouseSimple,   false);
            window.document.addEventListener('click',     mouseComplex,  false);
            

            移除事件监听器 方法移除之前使用 EventTarget.addEventListener() 注册的事件监听器。

            window.document.removeEventListener('keydown',   keyboard,     false);
            window.document.removeEventListener('mouseover', mouseSimple,  false);
            window.document.removeEventListener('click',     mouseComplex, false);
            

            caniuse

            【讨论】:

              【解决方案12】:

              这并不理想,因为它会删除所有内容,但可能会满足您的需求:

              z = document.querySelector('video');
              z.parentNode.replaceChild(z.cloneNode(1), z);
              

              克隆节点会复制其所有属性及其值,包括 内在(在线)听众。它不会复制使用添加的事件侦听器 addEventListener()

              Node.cloneNode()

              【讨论】:

              • 这真是太棒了
              • @AhmadAlfy 不是。它让未来的开发人员感到困惑,并且可能会在某些时候出现错误。你应该重新构建你的代码,这样就不需要了
              • @MartinDawson 我当然同意,但它在某些情况下可能很有用(例如,由依赖项添加的未命名回调)。它也应该记录在案,但并不是说不应该使用它。
              • 笑得很开心,非常聪明。不过似乎没有删除负载和提交处理程序。
              【解决方案13】:

              一个不那么匿名的选项

              element.funky = function() {
                  console.log("Click!");
              };
              element.funky.type = "click";
              element.funky.capt = false;
              element.addEventListener(element.funky.type, element.funky, element.funky.capt);
              // blah blah blah
              element.removeEventListener(element.funky.type, element.funky, element.funky.capt);
              

              自从收到 Andy 的反馈非常正确,但与许多示例一样,我希望展示这个想法的上下文扩展),这里有一个 less复杂的说明:

              <script id="konami" type="text/javascript" async>
                  var konami = {
                      ptrn: "38,38,40,40,37,39,37,39,66,65",
                      kl: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
                  };
                  document.body.addEventListener( "keyup", function knm ( evt ) {
                      konami.kl = konami.kl.slice( -9 );
                      konami.kl.push( evt.keyCode );
                      if ( konami.ptrn === konami.kl.join() ) {
                          evt.target.removeEventListener( "keyup", knm, false );
              
                          /* Although at this point we wish to remove a listener
                             we could easily have had multiple "keyup" listeners
                             each triggering different functions, so we MUST
                             say which function we no longer wish to trigger
                             rather than which listener we wish to remove.
              
                             Normal scoping will apply to where we can mention this function
                             and thus, where we can remove the listener set to trigger it. */
              
                          document.body.classList.add( "konami" );
                      }
                  }, false );
                  document.body.removeChild( document.getElementById( "konami" ) );
              </script>
              

              这允许有效的匿名函数结构,避免使用practically deprecated callee,并允许轻松删除。

              顺便说一句:在设置监听器后立即删除脚本元素是一种隐藏代码的可爱技巧/em> ;-)

              所以方法(更简单)是:

              element.addEventListener( action, function name () {
                  doSomething();
                  element.removeEventListener( action, name, capture );
              }, capture );
              

              【讨论】:

              • 这太复杂了。
              • @Andy 我同意,有点,但试图表明根本没有办法删除匿名函数。它必须以某种方式被引用(甚至被调用者(是坏的,M'Kay)正在引用该函数),因此提供了一个可以引用该函数的一种(其他)方式的示例 - 和,它是如何由可以同样存储以供以后参考的部分构建的(重要部分)。显然,一个真正的匿名函数在某种程度上是即时构建的,因此以后还必须知道哪个事件动作/类型以及是否使用了捕获。无论如何,这是一个更好的方法:-)
              • 非常适合我。我看不到另一种将参数传递给函数的方法,因为它不能是匿名的。
              【解决方案14】:
              window.document.onkeydown = function(){};
              

              【讨论】:

              • 为什么不 = 未定义?真正的坏蛋。
              • 这不会删除在addEventListener注册的任何处理程序。
              【解决方案15】:

              我相信这就是匿名函数的意义所在,它缺少名称或引用方式。

              如果我是你,我会创建一个命名函数,或者把它放在一个变量中,这样你就可以引用它。

              var t = {};
              var handler = function(e) {
                  t.scroll = function(x, y) {
                      window.scrollBy(x, y);
                  };
                  t.scrollTo = function(x, y) {
                      window.scrollTo(x, y);
                  };
              };
              window.document.addEventListener("keydown", handler);
              

              然后你可以删除它

              window.document.removeEventListener("keydown", handler);   
              

              【讨论】:

              • 感谢您的回复。我选择了: var handler; window.document.addEventListener("keydown", handler = function(e) { 但是我不明白为什么“this”没有引用事件监听器。事件监听器不应该是一个对象吗?
              • this 关键字可能会造成混淆。阅读它的好地方是quirksmode.org/js/this.html
              • 非常感谢。这是最有帮助的。
              • 我正在尝试这样做以阻止网站上真正持久的广告。我知道这是匿名函数的意义所在,但这并不意味着我不想知道该怎么做。
              • @bitkid:在处理函数内部(假设它不是箭头函数),this 指的是添加侦听器的元素,而不是事件本身(这将是参数e)。因此this === e.currentTarget。阅读developer.mozilla.org/en-US/docs/Web/API/EventTarget/…
              猜你喜欢
              • 2019-05-25
              • 1970-01-01
              • 1970-01-01
              • 2010-11-06
              • 1970-01-01
              • 2011-03-02
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多