jQuery点击事件触发多次

349
我正在尝试用Javascript编写一个视频扑克游戏,以此来掌握其基础知识,但我遇到了一个问题:jQuery点击事件处理程序会多次触发。它们附加在下注按钮上,在第一局游戏中下注时可以正常工作(仅触发一次);但是在下注第二手牌时,每次按下下注或放置下注按钮都会触发两次点击事件(因此每次按下将下注两倍的正确金额)。总体而言,当只按下一次下注按钮时,它遵循以下模式触发点击事件的次数,其中序列的第i项是从游戏开始时第i个手牌的下注:1、2、4、7、11、16、22、29、37、46,这似乎是n(n+1)/2 + 1,无论值不值得,我都不够聪明去理解它,我使用了OEIS。:)
以下是具有出现问题的点击事件处理程序的函数;希望它易于理解(如果不是,请告诉我,我也想变得更好)。
/** The following function keeps track of bet buttons that are pressed, until place button is pressed to place bet. **/
function pushingBetButtons() {
    $("#money").text("Money left: $" + player.money); // displays money player has left

    $(".bet").click(function() {
        var amount = 0; // holds the amount of money the player bet on this click
        if($(this).attr("id") == "bet1") { // the player just bet $1
            amount = 1;
        } else if($(this).attr("id") == "bet5") { // etc.
            amount = 5;
        } else if($(this).attr("id") == "bet25") {
            amount = 25;
        } else if($(this).attr("id") == "bet100") {
            amount = 100;
        } else if($(this).attr("id") == "bet500") {
            amount = 500;
        } else if($(this).attr("id") == "bet1000") {
            amount = 1000;
        }
        if(player.money >= amount) { // check whether the player has this much to bet
            player.bet += amount; // add what was just bet by clicking that button to the total bet on this hand
            player.money -= amount; // and, of course, subtract it from player's current pot
            $("#money").text("Money left: $" + player.money); // then redisplay what the player has left
        } else {
            alert("You don't have $" + amount + " to bet.");
        }
    });

    $("#place").click(function() {
        if(player.bet == 0) { // player didn't bet anything on this hand
            alert("Please place a bet first.");
        } else {
            $("#card_para").css("display", "block"); // now show the cards
            $(".card").bind("click", cardClicked); // and set up the event handler for the cards
            $("#bet_buttons_para").css("display", "none"); // hide the bet buttons and place bet button
            $("#redraw").css("display", "block"); // and reshow the button for redrawing the hand
            player.bet = 0; // reset the bet for betting on the next hand
            drawNewHand(); // draw the cards
        }
    });
}

请告诉我您是否有任何想法或建议,或者如果解决我的问题的方法类似于此处另一个问题的解决方法(我查看了许多标题类似的线程,但没有找到适合我的解决方案)。


var amount = parseInt(this.id.replace(/[^\d]/g,''),10);如果您将要多次使用元素的同一属性,请缓存该属性,不要一直重新查找。这种查找是很昂贵的。 - David Thomas
谢谢您的回复,以及有关缓存属性的提示。我在该函数内将player.money和player.bet设置为本地变量money和bet,并对其进行操作,我将更改我的其余代码以执行此操作。:) 如果您有时间,还可以解释一下您建议的amount初始化是做什么的;它看起来像一些正则表达式,但我很难理解它的含义。 - Gregory Fowler
@GregoryFowler - 和你的问题无关,但是……可能值得研究一下 JavaScript 的 switch 语句。 - Clayton
2
兄弟,你的函数每次被调用都会放置一个点击处理程序。如果你在每个回合中调用它,在第二个回合中你就有两个处理程序,依此类推。每个处理程序都会执行其工作,到第100轮时你将获得100个警报。 - Marco Faustinelli
这��因为在你的代码中,某处重新绑定了事件处理程序而没有先解除绑定。请参考此问题以获取类似情况的详细说明 - jpaugh
可能是点击事件触发多次问题,如何解决?的重复问题。 - jpaugh
29个回答

634

为了确保一个点击只触发一次,请使用以下代码:

$(".bet").unbind().click(function() {
    //Stuff
});

40
好的,Rob,你昨天在哪里呀? ;) 这正是我一直在寻找的,不知道为什么之前没找到。但这还是个意外之喜,因为我学到了很多其他的东西。 - Gregory Fowler
2
抱歉,我在这个问题上也转了几天的弯路。我仍在寻找为什么它会发生的逻辑原因。 - Rob
6
这个人经历了很多事情之后才做到这一点。而在我的情况下,我使用了类似于以下代码的等效语句:$(".bet").off().on() - MadTurki
8
如下所述,jroi_web说这种方法已经过时。请参考@trolle的答案以获得更近期的解决方案。 - Pascal
1
一个点击处理程序不是一次性的可抛弃的东西。特别是当它是问题中显示的笨重的if-if-if时。你应该将其附加并让它在页面存在的时间内完成其工作。 - Marco Faustinelli
显示剩余10条评论

457

.unbind() 已过时,请使用.off() 方法代替。只需在调用 .on() 之前调用 .off() 即可。

这将删除所有事件处理程序:

$(element).off().on('click', function() {
    // function body
});

仅移除已注册的“click”事件处理程序:

$(element).off('click').on('click', function() {
    // function body
});

13
应使用较新版本的jQuery,因为unbind、die或live已被弃用。 - jroi_web
这确实解决了问题,但我有很多点击事件,这是我第一次遇到问题。你能否解释一下为什么会发生这种情况?是否建议始终删除事件处理程序? - Ben

176

.one()

更好的选择是.one()

处理程序每个事件类型每个元素最多只会被执行一次。

$(".bet").one('click',function() {
    //Your function
});
在存在多个类别且每个类别需要单击一次的情况下,
$(".bet").on('click',function() {
    //Your function
    $(this).off('click');   //or $(this).unbind()
});

11
最佳答案,你救了我免受愚蠢的黑客攻击,这更好,因为如果你在不同的位置有多个onclick事件绑定到相同的元素,这不会影响其他部分,而unbind()off()将会破坏其他的onclick。再次感谢。 - Fanckush
4
尝试了所有答案后,只有这个对我有效。 - neversion
16
需要注意的是,如果您希望函数仅在每次点击时触发一次,但仍然希望在随后的点击中继续触发,则此函数将仅在页面存在期间触发一次。因此,需要使用mtl提供的off().on()方法来实现。 - Derek Hewitt
2
@munchschair,这可能有多种原因。在彼此内部有相同类的多个元素。或者在迭代中注册了多次单击处理程序。 - Shaunak D
4
对我来说没有用 - 仍然存在多次点击触发 - 这毫无意义,因为只有一个具有ID的按钮元素,只有一个事件会被捕获(点击事件)。我以为这是jQuery的bug,但可能是Bootstrap模态对话框的bug。 - MC9000
显示剩余4条评论

120

如果你发现使用 .off() .unbind() 或 .stopPropagation() 仍然无法解决你的具体问题,那么尝试使用 .stopImmediatePropagation() 在你只想处理事件而不想让它冒泡或影响其他已被处理的事件时非常有效。像这样:

$(".bet").click(function(event) {
  event.stopImmediatePropagation();
  //Do Stuff
});

起作用了!


这对我有所帮助,可以防止事件触发两次,但也给我带来了很大的麻烦。事实证明,如果您在附加到jQuery UI Dialog字段的事件处理程序中使用event.stopImmediatePropagation(),那么由于某种原因,对话框将无法引用全局变量的最新实例,而是使用jQuery UI Dialog渲染之前版本的全局变量。这意味着,例如,如果您的全局变量在jQuery UI Dialog渲染时声明但未初始化,则它将显示为未初始化的jQuery UI Dialog事件处理程序。 - UkraineTrain
2
如果在呈现jQuery UI对话框之前将某个全局变量的值分配为16,然后该值更改为55,则即使此时不再是16,在jQuery UI对话框事件处理程序中仍会显示16。因此,这看起来像是一个人们应该注意的jQuery UI bug。 - UkraineTrain
1
对于非常特定的情况,甚至难以描述。它完全按照您所说的方式工作:“在不影响任何其他事件的情况下工作”。谢谢! :) - Toms Bugna
2
我查看了超过15个答案,最终找到了这个e.stopImmediatePropagation - WEshruth
我只是在寻找.stopPropagation(),但这个答案还快速概述了需要考虑的方法! - Bence Szalai

21

一个事件如果被多次注册(即使是到同一个处理函数),它将会被触发多次。

例如,$("ctrl").on('click', somefunction) 如果每次页面部分刷新时都执行此代码,则该事件也会被注册多次。因此,即使只单击一次 ctrl,它也可能会多次执行 "somefunction" - 它执行的次数取决于被注册了多少次。

对于在 JavaScript 中注册的任何事件都是如此。

解决方法:

确保只调用一次 "on"。

如果由于某种原因无法控制架构,则可以执行以下操作:

$("ctrl").off('click'); $("ctrl").on('click', somefunction);


你真正想说什么? - Somnath Kharat
这并没有解释在这种情况下发生了什么。该按钮具有一个唯一的ID和仅1个事件(它不会被多次调用,因为它只能被单击一次)。这是一个错误,只出现在jQuery对话框中(非常容易重现)。 - MC9000
3
我们不知道函数“pushingBetButtons”是否只被调用了一次还是多次。如果它被调用多次,那么事件将被注册多次,因此即使按钮只被点击一次,它也会触发多次。 - Kalpesh Popat

18
如果你在每次“点击”时调用该函数,那么它会在每次调用中添加另一组处理程序。 使用jQuery添加处理程序并不像设置“onclick”属性的值。一个可以添加任意数量的处理程序。

4
你的回答给了我正确的方向并让我学到了很多,但是不幸的是,这些对我使用jQuery来解决问题还不够。 :) 我尝试过使用on/off、live(和delegate)/die以及one方法,然后又用addEventListener/removeEventListener,但最好的结果只是减缓了处理程序数量的指数级增长。最终,我只好暂时使用onclick来解决我的问题。但是,从你的回答中,我仍然学到了很多关于JavaScript事件模型的知识,比如捕获/冒泡,所以非常感谢。谢谢。 :) - Gregory Fowler

11

6

为了避免点击事件触发过多次,我们需要使用 stopPropagation()

$(this).find('#cameraImageView').on('click', function(evt) {
   evt.stopPropagation();
   console.log("Camera click event.");
});

阻止事件冒泡至DOM树,从而防止通知任何父处理程序的发生。此方法不接受任何参数。

我们可以使用event.isPropagationStopped()来确定是否曾在该事件对象上调用过此方法。

此方法也适用于使用trigger()触发的自定义事件。请注意,这不会阻止同一元素上的其他处理程序运行。


4

每当我处理这个问题时,我总是使用:

$(".bet").unbind("click").bind("click", function (e) {
  // code goes here
}

这样我可以在同一个步骤中解绑和重新绑定。

4

I had a problem because of markup.

HTML:

<div class="myclass">
 <div class="inner">

  <div class="myclass">
   <a href="#">Click Me</a>
  </div>

 </div>
</div>

jQuery

$('.myclass').on('click', 'a', function(event) { ... } );

你会注意到我在html中两次使用了相同的类'myclass',因此它会针对每个div实例调用click。


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