题 调用event.preventDefault()后,为什么用JavaScript切换复选框/单选按钮失败?


考虑以下示例代码:

<span>
    <input type="checkbox">
</span>

$('span').click(function(e) {
    e.preventDefault();
    $(':checkbox')[0].checked = true;
});

小提琴

据我所知,这应该发生:

  • preventDefault() 应该阻止浏览器的默认行为检查复选框,即使事件处理程序附加在DOM层次结构的上方。这部分工作正常。
  • 设置 .checked = true 应该工作,我相信,它应该独立于浏览器对我已取消的事件的默认操作。这部分似乎有点儿,好像是 preventDefault() 正在影响它 - 删除 preventDefault() 它按预期工作。

复选框始终未选中的实际原因是什么?

我已经在Chrome 33和Firefox 27上进行了测试,所以这似乎不是一个浏览器错误。

这个问题主要是由于好奇心扩展我的DOM /事件模型知识。我不想要解决方法,而是想知道为什么这个例子失败了。


12
2018-02-25 13:18


起源


看到 小提琴 当你点击文字 e.preventDefault() 不与默认行为接口 - Satpal
@Satpal点击事件必然不会跨越到孩子,所以实际发生了什么。 - Jai
我真的很困惑发生了什么,好问题! - A. Wolff
@Satpal谢谢,似乎进一步证明了kamilkp之前回答的问题,复选框似乎在之后无法检查 click 由于鼠标事件导致的事件,但不确定(AFAIK)在点击事件之后没有事件。 - Fabrício Matté
类似 stackoverflow.com/questions/7435142/... - Musa


答案:


给出你的HTML:

<span>
    <input type="checkbox">
</span>

和你的代码:

$('span').click(function(e) {
    e.preventDefault();
    $(':checkbox')[0].checked = true;
});

事件冒泡是你的问题。

点击时 checkbox,click事件传播到 spane.preventDefault() 是从事件传播的事件对象的调用 checkbox

于是 preventDefault() 是执行反对 checkbox 本身,防止它被检查。

一个点击事件 checkbox 被“取消”的方式 preventDefault 它将保持取消直到事件流程完成。 (有关详细信息,请参阅答案底部)

如果你在跨度上应用了一些样式,你会注意到点击周围 checkbox 您的代码按预期工作但单击 checkbox 由于上述原因,本身会复制您的问题。


DEMO  - 原始小提琴+风格。点击 span 很好, checkbox 不是


根据 关于preventDefault()的MDN文档

调用 preventDefault 在事件流程的任何阶段取消   事件,意味着通常采取的任何默认操作   由于事件的实现不会发生。

DOM Level 2 Spec 还注意到:

如果一个事件可以取消,那么 preventDefault 方法用于   表示该事件将被取消,表示任何默认操作   由于事件通常采取的实施不会   发生。 如果在事件流的任何阶段,则使用preventDefault方法   被称为事件被取消

DOM Level 3事件规范 例5中的注释:

与之关联的默认操作 click 活动 <input type="checkbox"> 元素切换 checked 该元素的IDL属性值。如果 click 事件的默认操作被取消,然后该值将恢复到以前的状态。


4
2018-02-25 13:43



那样的 click 事件处理程序 span 永远不会被称为。 - kamilkp
“因此,对复选框本身执行preventDefault(),防止它被检查。” - 是的,这是我的预期行为。问题是没有对输入应用preventDefault,问题是为什么设置检查状态的代码行不能按预期工作 preventDefault()。 - Fabrício Matté
@FabrícioMatté: the line of code which sets the checked state does not work 它不起作用,因为跨度捕获的第一个事件是来自复选框的冒泡点击事件,它被告知没有做它的默认行为。当您的代码行执行时,事件链仍处于活动状态,并且防止默认行为的指令也是如此。 - Nope
猜猜我们沟通得不好。让我们尝试另一种方式,我知道点击复选框会依次冒泡到跨度 preventDefault 点击复选框。问题是,为什么 .checked = true 失败? - Fabrício Matté
@FabrícioMatté:看看你的问题中的SO帖子的答案。 The misunderstanding probably occurs because you are thinking of the event as firing after the actual click has been fully processed (i.e. the checkbox has been toggled) while in reality the event model stipulates that the handler fires while the event is being processed. 这就是我想告诉你的 By the time your line of code executes the event chain is still active and the instructions to prevent the default behaviour are too 另一个人解释得更好:) - Nope


将部分代码包装在一个 setTimeout 你可以看到这个诀窍 http://jsfiddle.net/y7C77/

$('span').click(function(e) {
    e.preventDefault();
    setTimeout(function () {
        $('input').prop('checked', true);
    }, 1);
});

preventDefault() 取消默认操作,我认为即使您手动执行浏览器操作,它也可以取消它(在这种情况下:更改 checked属性)。


3
2018-02-25 14:18



setTimeout 因为内部函数在事件流完全完成后执行而调用是有效的 preventDefault 在宣传上 checkbox 事件对象。很好的工作:) - Nope
我在思考同样的问题,但这是一个理论问题。如果有好的解决方法,以防万一将来需要它。 - Fabrício Matté


这是我所感受到的预期行为,因为当您说event.preventDefault()时,您指示浏览器在此事件中不对其执行任何操作。因此,即使在您明确选中复选框的语句之后,浏览器也不会对其执行任何操作。 我更改事件时可以看到差异如下:

$('span').click(function(e) {
     e.preventDefault();
     //$(':checkbox')[0].checked = true;
     setTimeout(function(){$(':checkbox')[0].checked = true;});
});

使用超时我们正在更改事件外的checked属性,因此检查它。


2
2018-02-25 14:17





你必须这样做 preventDefault 都 click 和 mouseup 并设置选中 mouseup

尝试:

$('span').click(function(e) {
    e.preventDefault();
}).mouseup(function(e){
    e.preventDefault();
    $(':checkbox')[0].checked = true;
});

1
2018-02-25 13:26



还有其他几个更好的解决方法.....问题是 复选框始终未选中的实际原因是什么? 而你还没有回答。 - Jai
你在打电话 preventDefault 在跨度上。单击输入时,单击事件会使DOM冒泡。并在 span's 事件监听器已经发生了 input。该复选框被检查,然后在此之后不久检查,因此您甚至看不到它。 - kamilkp
@kamilkp为什么不加以检查? - A. Wolff
@kamilkp是的,这是有道理的,虽然还有一些感觉错了。 mouseup 发生在之前 click,和 preventDefaultING mouseup 没有修复行为 click: jsfiddle.net/V3pW6/1 - Fabrício Matté