为什么我的jQuery点击事件会被调用两次?

18

为什么我的jQuery点击事件会触发两次?

HTML

<ul class=submenu>
    <li><label for=toggle><input id=toggle type=checkbox checked>Show</label></li>
</ul>

JavaScript

$("ul.submenu li:contains('Show')").on("click", function(e) {
    console.log("toggle");
    if ($(this).find("[type=checkbox]").is(":checked")) console.log("Show");
    else console.log("Hide");
});

这是我在控制台中得到的内容:

toggle                     menu.js:39
Show                       menu.js:40
toggle                     menu.js:39
Hide                       menu.js:41


> $("ul.submenu li:contains('Show')")
[<li>​                                            ]
    <label for=​"toggle">​
      <input id=​"toggle" type=​"checkbox" checked>​
      "Show"
    </label>​
</li>​

在这里尝试时,它只会触发一次:http://jsfiddle.net/LRjUc/ - Dominic Green
@DominicGreen:这取决于您点击标签还是复选框(或 li)。如果您点击标签,则至少在 Chrome 上,您会同时收到两个事件。 - T.J. Crowder
是的,有点难看到,但如果你在控制台上选择最后一段文本,你就可以看到事件在那之后被触发了两次。 - shuji
5个回答

46

1
是的,你说得对。我也可以仅为复选框触发事件,例如$("ul.submenu li:contains('Show borders') [type=checkbox]").on("click",callback); ,非常感谢你的回答。 - shuji
@shuji:啊,好的,我以为你把它钩在li上有特殊的原因。 :-) - T.J. Crowder
不是的,只是这是我第一次在菜单中使用复选框,所以我习惯于直接绑定到li。 - shuji
1
你能否在这里使用 change 事件呢?这可能也会解决你的问题:http://jsbin.com/iRozUd/3 - Andrew Whitaker
@Andrew Whitaker添加到复选框绑定的内容具有完全相同的结果,但我认为这对于复选框来说更加合适。 - shuji
3
这简直节省了我几个小时的时间和烦恼。谢谢,伙计。 - rgin

9

我建议您在 input[type="checkbox"] 上使用 change 事件,该事件仅会触发一次。因此,针对上述问题,您可以执行以下操作:

$("#toggle").on("change", function(e) {
  if ($(this).is(":checked"))
    console.log("toggle: Show");
  else
    console.log("toggle: Hide");
});

这是使用querySelector的原生JS版本,不兼容较旧版本的IE:

https://jsfiddle.net/ssrboq3w/

document.querySelector('#toggle').addEventListener('change',function(){
  if(this.checked)
    console.log('toggle: Show');
  else
    console.log('toggle: Hide');
});

https://jsfiddle.net/rp6vsyh6/


3
应该接受这个答案,因为它听起来更加合乎逻辑。 - PIIANTOM

5
input 标签被嵌套在 label 标签内时,会出现此行为:
<label for="toggle"><input id="toggle" type="checkbox" checked>Show</label>

如果把复选框放在

<label for="toggle">Show</label>
<input id="toggle" type="checkbox" checked>

不幸的是,即使分离后,问题仍然存在。 - abhishake

0

不确定为什么没有提到这一点。但是如果:

  1. 您不想将 input 移动到 label 外面(可能是因为您不想更改 HTML)。
  2. 通过 e.target.tagName 或者 e.target 进行检查对您来说不起作用,因为您在标签内部有其他元素(在我的情况下,它有包含一个 path 的 SVG 的 span,所以 e.target.tagName 有时显示 SVG,有时显示 PATH)。
  3. 您希望单击处理程序保留在 li 上(可能是因为除了复选框之外,您还有其他项目在 li 中)。

那么这应该很好地解决问题。

$('label').on('click', function(e) {
  e.stopPropagation();
});
$('#toggle').on('click', function(e) {
  e.stopPropagation();
  $(this).closest('li').trigger('click');
});

然后,您可以编写自己的`li`单击处理程序而不必担心事件被触发两次。个人而言,我更喜欢使用`data-selected`属性,每次单击`li`时将其从`false`更改为`true`,反之亦然,而不是依赖于`input`的值:
$('ul.submenu li').on('click', function() {
  let _li = $(this),
      ticked = _li.attr('data-selected');
  
  ticked = (ticked === 'false') ? true : false;
  _li.attr('data-selected', ticked);
  _li.find('#toggle').prop('checked', ticked);
});

0
我发现当我在代码中定义了点击(或更改)事件,并且该位置被多次调用时,就会出现这个问题。将定义移动到文档准备好的点击事件中,你就可以解决问题了。

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