无法解除绑定jQuery自定义事件处理程序

3
我在我的页面上有一块标记,表示一个视图,以及一个与该视图关联的JS控制器函数。(这些是Angular,但我不认为这很重要。) 控制器代码监听从应用程序中其他地方发出的自定义事件,并使用一些特定于控制器的逻辑处理该事件。
我的问题是控制器的事件处理程序被附加太多次了:每次重新激活视图时都会被附加,导致处理程序每次自定义事件被触发时都运行多次。我希望处理程序每次事件只运行一次。
我已经尝试使用.off()来解除绑定处理程序,然后再绑定它; 我已经尝试使用.one()来确保处理程序只运行一次; 我已经阅读了关于$.proxy()与.off()here交互的内容。
这里是我的代码草稿:
// the code inside this controller is re-run every time its associated view is activated
function MyViewController() { 

    /* SNIP (lots of other controller code) */

    function myCustomEventHandler() {
        console.log('myCustomEventHandler has run');
        // the code inside this handler requires the controller's scope
    }

    // Three variants of the same misbehaving event attachment logic follow: 

    // first attempt
    $('body').off('myCustomEvent', myCustomEventHandler);
    $('body').on('myCustomEvent', myCustomEventHandler);
    // second attempt
    $('body').one('myCustomEvent', myCustomEventHandler);
    // third attempt
    $('body').off('myCustomEvent', $.proxy(myCustomEventHandler, this));
    $('body').on('myCustomEvent', $.proxy(myCustomEventHandler, this));
    // all of these result in too many event attachments

};

// ...meanwhile, elsewhere in the app, this function is run after a certain user action
function MyEventSender() {
    $('body').trigger('myCustomEvent');
    console.log('myCustomEvent has been triggered');
};

在我的应用程序中不停地点击并切换到有问题的视图五次,然后执行运行MyEventSender的操作后,我的控制台将如下所示:

myCustomEvent has been triggered
myCustomEventHandler has run
myCustomEventHandler has run
myCustomEventHandler has run
myCustomEventHandler has run
myCustomEventHandler has run

如何让它看起来像这样:
myCustomEvent has been triggered
myCustomEventHandler has run

???


据我所知,Angular事件循环和jQuery事件循环完全无关(除了它们都连接到DOM)。你应该在控制器外将事件附加到<body>标签中,在 $(function() { ... }) 中调用。 - rossipedia
@KevinB 你有没有切换到麻烦的视图几次?实际上我刚试了一下,还是只有一个... - Ruan Mendes
@KevinB:你没有重新加载视图,因此调用控制器绑定多次。这就是问题所在。 - rossipedia
@tuff 你能看一下Kevin的例子并解释一下你的代码和他的之间的区别吗? - Ruan Mendes
@tuff:你需要提供更多的代码。请将一个能够重现问题的完整工作示例放在类似jsfiddle.net或jsbin.com这样的网站上,这样我们才能更好地了解情况。 - rossipedia
显示剩余5条评论
3个回答

3

给你的事件添加命名空间,当您重新运行控制器时,简单地删除所有带有该命名空间的事件。

jsbin

$('body').off('.controller');
$('body').on('myCustomEvent.controller', myCustomEventHandler);

这可能比找出要注销的代理处理程序更容易。 - Ruan Mendes
请看我的回答,了解为什么原帖中的代码没有注销。 - Ruan Mendes
这个不起作用:http://jsbin.com/adecul/17/edit :( 我仍然看到“myCustomEventHandler已运行”x7。 - tuff
解除绑定时摆脱处理程序。http://jsbin.com/adecul/18/edit 如果您想传递处理程序,则必须为每个事件绑定编写一行,这对我来说似乎不太可维护。 - Kevin B
难道不应该是 $('body').off('myCustomEven.controller'); 这样你就不会删除具有相同命名空间的其他事件了吗? - Ruan Mendes
显示剩余2条评论

2
问题在于当多次调用function MyViewController(){}时,你会得到一个单独的 myCustomEventHandler 实例(附加到当前闭包),因此将其传递给 $.off 不会注销先前的处理程序。 KevinB 的答案,使用事件命名空间,建议删除特定处理程序而不需要知道安装了哪个处理程序。如果您能在元素被删除/隐藏时取消注册事件,那么您将拥有要注销的函数的引用,而不会冒着删除其他代码可能添加到同一事件命名空间的处理程序的风险。毕竟,事件命名空间只是一个全局字符串池,容易受到名称冲突的影响。
如果您使函数成为全局函数,则它也可以工作(除了看起来您需要闭包),但我只是展示它以解释问题,使用命名空间。
function myCustomEventHandler() {
    console.log('myCustomEventHandler has run');
    // the code inside this handler requires the controller's scope
}

function MyViewController() { 

    // first attempt
    $('body').off('myCustomEvent', myCustomEventHandler);
    $('body').on('myCustomEvent', myCustomEventHandler);
    // second attempt
    $('body').one('myCustomEvent', myCustomEventHandler);
    // third attempt
    $('body').off('myCustomEvent', $.proxy(myCustomEventHandler, this));
    $('body').on('myCustomEvent', $.proxy(myCustomEventHandler, this));

}

// ...meanwhile, elsewhere in the app, this function is run after a certain user action
function MyEventSender() {
    $('body').trigger('myCustomEvent');
    console.log('myCustomEvent has been triggered');
}
MyViewController();
MyViewController();
MyEventSender();

之前的想法

问题之一是,你没有将相同的函数传递给$.on$.off,因此在这种情况下off不会注销任何内容

这不是问题,保留答案以供参考,因为它并不完全直观。$.proxy似乎会返回对相同绑定函数的引用,如果传递了相同的函数和上下文。 http://jsbin.com/adecul/9/edit


1
这正是我所期望的(我期望看到它被执行3次),但实际上我只看到它被执行了一次,请参见我在另一个评论中提供的jsbin。他甚至得到了超过三个的结果,这表明发生了其他事情,不属于所展示的内容。 - Kevin B
@KevinB 我运行了你的jsbin,但我不确定在那种情况下视图是否被重新加载。我调用了 MyEventSender(); 三次,但无法重现这个问题。 - Ruan Mendes
是的,我对给定的代码所做的任何操作都没有产生OP所看到的结果。 - Kevin B
当您多次运行控制器时,它没有清理旧事件。这是链接:http://jsbin.com/adecul/1/。 - Kevin B

2

您可以在主控制器中监听作用域销毁事件。

function MyViewController($scope) { 
    function myCustomEventHandler() {
        console.log('myCustomEventHandler has run');
        // the code inside this handler requires the controller's scope
    }

    $('body').on('myCustomEvent', myCustomEventHandler);    

    $scope.$on("$destroy", function(){
        $('body').off('myCustomEvent', myCustomEventHandler);   
        //scope destroyed, no longer in ng view
    });
}

编辑 这是一个AngularJS的解决方案。当您从页面到页面移动时,ngview会不断地被加载。由于该函数被重复调用,它会一遍又一遍地附加事件。您需要做的是在某人离开视图时解除/删除事件的绑定。您可以通过连接到作用域的$destroy(带有美元符号)事件来实现这一点。您可以在此处阅读有关此事件的更多信息:$destroy文档


我喜欢这个回答,现在我很抱歉我的问题太具体地涉及了jQuery。 我对为什么jQuery没有按预期工作感到好奇,但是这个答案非常好地解决了我的问题。 - tuff
这很好,我在我的答案中建议了这个,但我不知道足够关于angular的$destroy事件。没有办法创建冲突(就像事件命名空间一样)。 - Ruan Mendes
@tuff,尽管我不想显得高人一等(如果是的话请原谅),但我建议您采纳我的答案。原因是:如果您采用了最高票答案并且有人进入然后离开视图,那么该事件处理程序仍将绑定在某个地方。即使他们再也不会回到该视图。然而,在我的答案中,它将被永久删除。 - Mathew Berg

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