JavaScript中匿名函数的removeEventListener用法

152

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

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浏览器。


为什么不保存这个函数?事件处理程序可能不是匿名函数。 - kirilloid
2
我知道有点晚了,但是你也可以使用 Node.setUserData/Node.getUserData 方法来存储关于元素的数据。例如,当你需要设置一个匿名监听器(并且能够删除它)时,首先将userdata设置为匿名函数 (Elem.setUserData('eventListener', function(e){console.log('Event fired.');}, null); ,然后执行 Elem.addEventListener('event', Elem.getUserData('eventListener'), false); ... 同样适用于removeEventListener。希望你能看明白这个过程。 - Chase
根据之前的评论,我猜这仅适用于Firefox...我刚尝试了IE8(IE9未知),Safari 5.1.2,Chrome(?),Opera 11..都不行。 - Chase
可能是JavaScript:删除事件侦听器的重复问题。 - Heretic Monkey
@ Heretic Monkey 这个链接与本题无关:1)它没有“keydown”事件;2)所有答案都包括鼠标点击! - Apostolos
18个回答

132

您可以给传递的函数命名,并在removeEventListener中使用该名称,例如:

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

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

编辑2: arguments.callee现已被弃用(https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/arguments/callee


2
这很好,因为它保留了匿名函数的优点(不会污染命名空间等)。 - bompf
4
我尝试在WinJS应用程序中运行此代码,却出现了以下错误:"在严格模式下不允许访问参数对象的 'callee' 属性"。 - Valentin Kantor
1
@ValentinKantor:这是因为代码中有一个"use strict"语句,而在严格模式下无法使用callee。 - OMA
42
给内联函数起个名字,这样您就可以在不使用arguments.callee的情况下引用它: button.addEventListener('click', function handler() { this.removeEventListener('click', handler); }); - Harry Love
5
根据Mozilla的说法:“警告:ECMAScript(ES5)的第五版在严格模式下禁止使用arguments.callee()。通过为函数表达式命名或在必须调用自身的函数处使用函数声明来避免使用arguments.callee()。” - dude
显示剩余6条评论

104

我认为匿名函数的重点在于它没有名称或引用方式。

如果我是你,我会创建一个命名函数,或将其放入变量中以便您可以引用它。

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

3
谢谢您的回复。我选择了以下代码:var handler; window.document.addEventListener("keydown", handler = function(e) {但是我不明白为什么 "this" 没有引用事件监听器。难道事件监听器不应该是一个对象吗? - bitkid
2
this 关键字可能会令人感到困惑。阅读有关它的良好资料可以访问 http://www.quirksmode.org/js/this.html 。 - Adam Heath
1
@bitkid:在处理程序函数内(假设它不是箭头函数),this 指的是添加监听器的元素,而不是事件本身(这将是参数 e)。因此,this === e.currentTarget。请阅读 https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#The_value_of_this_within_the_handler - chharvey
@Adam Heath,恭喜!你解决了一个问题(真正的“痛点”),这里数十个人都无法或者认为他们已经做到了!!而且方法最简单! - Apostolos
我无法理解为什么这个被接受为答案:显然,如果你使用一个命名函数(处理程序),你不会在匿名函数上删除事件侦听器。你根本没有回答问题。Otto Nascarella的答案应该被接受。 - Chrysotribax
显示剩余3条评论

93

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

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

7
漂亮的解决方案! - Eric Norcross
3
也许这不是正确的方法,但这是最容易的事情。 - Vignesh
1
这在VueJS指令的上下文中有效。我创建了一个方法,只想让它运行一次。 - Riza Khan
2
我很好奇为什么这可能不是正确的方式。 - Fernanda Parisi
这个答案需要将函数变为非匿名的,但这似乎是最容易维护的。另一个选择是回答https://dev59.com/4m445IYBdhLWcg3wRoLH#39279172,但它更冗长,并使用真正的匿名对象。 - Mikko Rantalainen

38

1
很酷,直到你发现IE和边缘的任何版本都不支持此功能<16。至少在5年内我们可以使用它,因为那时IE将(即:应该)被废弃,Edge将取代它并使用Webkit引擎而不是他们自己的“EdgeHTML”东西。 - SidOfc
1
使用这个DOM Level 4 entries的polyfill,你应该没问题了。https://www.npmjs.com/package/dom4 - shunryu111

19

有一种新方法可以执行此操作,最受欢迎的浏览器的最新版本(Safari除外)支持它。

查看caniuse以获取更新的支持信息。

更新:现在也受到Sefari(版本15^)的支持。

我们可以添加一个名为signal的选项到addEventListner中,并将来自AbortControllersignal分配给该选项,然后您可以稍后调用abort()方法来取消该信号。

以下是一个示例。

我们创建一个AbortController

const controller = new AbortController();

然后我们创建eventListner并传递选项signal
document.addEventListener('scroll',()=>{
    // do something
},{signal: controller.signal})

稍后要删除 eventListner,我们需要调用:

controller.abort()

2
不知道这个,但它很酷!AbortController最初用于取消.fetch()请求,现在也可以用于删除事件监听器。https://css-tricks.com/using-abortcontroller-as-an-alternative-for-removing-event-listeners/还有一个AbortController的ponyfill/polyfill:https://github.com/mo/abortcontroller-polyfill这里还有一个更全面的polyfill:https://github.com/mysticatea/event-target-shim - Mark

8
window.document.removeEventListener("keydown", getEventListeners(window.document.keydown[0].listener));  

可能有几个匿名函数,keydown1

警告:仅适用于Chrome Dev Tools,不能在代码中使用链接


27
getEventListeners 似乎是 Chrome Dev-tools 的一部分,因此除了调试之外并不能用于其他任何目的。 - DBS
2
刚试了一下,确认它只能在开发者工具中使用,而不能在页面内的脚本中使用。 - Andres Riofrio

7
这并不是理想的解决方案,因为它会删除所有内容,但可能适合您的需求:
z = document.querySelector('video');
z.parentNode.replaceChild(z.cloneNode(1), z);

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

Node.cloneNode()


1
@AhmadAlfy 不是真的。这会让未来的开发人员感到困惑,并可能在某些时候产生错误。相反,你应该重新构建你的代码,以便不需要这样做。 - Martin Dawson
3
@MartinDawson 我当然同意,但在某些情况下可能会很有用(例如由依赖项添加的未命名回调)。它也应该被记录下来,但并没有说不应该使用它。 - Ahmad Alfy
1
这真是太聪明了,让我忍不住笑出声来。不过似乎没有移除负载和提交处理程序。 - Devon Guerrero

2
为了更加贴近现代的方法,可以这样做:
//one-time fire
element.addEventListener('mousedown', {
  handleEvent: function (evt) {
    element.removeEventListener(evt.type, this, false);
  }
}, false);

7
可以给一个解释会更好。 - Poul Bak
1
这似乎有效,因为addEventListener()允许签名,其中第二个参数是一个包含名为handleEvent()的函数的对象,在这种情况下,this指向该对象(请参阅https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#syntax)。由于正是使用此相同的对象来注册侦听器,因此由`this`指向的引用是删除侦听器的正确引用。如果`addEventListener()`的第二个参数是一个函数(典型用法),那么`this`就会引用该函数内部的事件,而这种技巧无法用于删除侦听器。 - Mikko Rantalainen

2
一个不那么匿名的选项
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的反馈(非常正确,但像许多例子一样,我希望展示这个想法的上下文扩展),这里有一个更简单的阐述:
<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>

这允许有效地匿名函数结构,避免使用已经被实际淘汰的 callee,并且可以轻松删除。
顺便说一下:在设置监听器后立即删除脚本元素是一个巧妙的技巧,可以隐藏代码,以防止被窥探的眼睛发现(会破坏惊喜;-)
因此,更简单的方法是:
element.addEventListener( action, function name () {
    doSomething();
    element.removeEventListener( action, name, capture );
}, capture );

2
这太复杂了。 - pronebird
@Andy 我同意,但是我试图表明没有办法删除匿名函数。它必须以某种方式被引用(即使调用者(不好,M'Kay)也在引用该函数),因此提供了一个仅有的(其他)引用该函数的方法示例 - 以及如何构建可以同样存储以供以后引用的部分(这是重要的部分)。显然,真正的匿名函数是在运行时构建的,因此还必须知道稍后使用哪个事件操作/类型以及是否使用了捕获。无论如何,这是一种更好的方法 :-) - Fred Gandt
对我来说完美地工作了。我看不到其他将参数传递给函数的方法,因为它不能是匿名的。 - nicodemus13

1

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

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

鼠标,键盘事件示例测试在Web控制台中:

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

removeEventListener方法可以移除之前通过EventTarget.addEventListener()注册的事件监听器。

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

{{链接1:caniuse}}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接