【问题标题】:Why the event handler is executed more than once with JQuery on() API?为什么使用 JQuery on() API 多次执行事件处理程序?
【发布时间】:2013-07-06 08:01:53
【问题描述】:

当我使用 on() 监听事件时,我发现事件处理程序被多次调用。代码如下:

html:

<div id="container">
<div>
    <div><input type="text"/></div>        
</div>    
</div>

JS:

var $div = $('#container'), $input = $('input');

$div.on('validate', 'div', function() {
    console.log('div');
});

$input.blur(function() {
    $(this).trigger('validate');
});

Here is the updated DEMO

  • 然后“验证”处理程序被调用两次。
  • 当我深入研究 JQuery 源码,我发现JQuery会找到事件的父节点 目标并检查选择器是否匹配。
  • 如果匹配选择器, 处理程序队列将再推一个handleobj。在里面 例如,在事件目标和侦听器元素 div#contianer 之间, 有两个分层 div。
  • 结果,处理程序队列有两个 事件处理器是同一个事件处理器函数实例。

我的问题是:如何正确使用选择器来防止事件处理程序被多次调用?谢谢。

我认为要回答这个问题,我们应该仔细阅读JQuery中的相关源码:

// Determine handlers that should run if there are delegated events
        // Avoid non-left-click bubbling in Firefox (#3861)
        if ( delegateCount && !(event.button && event.type === "click") ) {

            // Pregenerate a single jQuery object for reuse with .is()
            jqcur = jQuery(this);
            jqcur.context = this.ownerDocument || this;

            for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {

                // Don't process events on disabled elements (#6911, #8165)
                if ( cur.disabled !== true ) {
                    selMatch = {};
                    matches = [];
                    jqcur[0] = cur;
                    for ( i = 0; i < delegateCount; i++ ) {
                        handleObj = handlers[ i ];
                        sel = handleObj.selector;

                        if ( selMatch[ sel ] === undefined ) {
                            selMatch[ sel ] = (
                                handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel )
                            );
                        }
                        if ( selMatch[ sel ] ) {
                            matches.push( handleObj );
                        }
                    }
                    if ( matches.length ) {
                        handlerQueue.push({ elem: cur, matches: matches });
                    }
                }
            }
        }

【问题讨论】:

  • 添加代码示例链接:jsfiddle.net/ianjiang/egcVb
  • 我想你看到的是事件委托/传播。一些事件将 DOM 层次结构向上传播到父级。您可以使用event.stopPropagation() 停止传播。
  • 我不知道为什么,但如果你在console.log 语句之后执行return false;,它只会记录一次。
  • 是的,我意识到 JQuery 自定义事件通过触发器可以冒泡。但这是设计使然吗?一旦选择器匹配,处理程序队列将再添加一个相同的处理程序。

标签: jquery events


【解决方案1】:

这意味着:在 $div 上绑定 'validate' 事件,但也为每个 div 后代包含它。

$div.on('validate', 'div', function() {
    console.log('div');
});

见jQuery on,参数selector:

一个选择器字符串来过滤被选元素的后代 触发事件。如果选择器为空或省略,则事件 总是在到达被选元素时触发。

使用这个:

$div.on('validate', function() {
    console.log('div');
});

...您将只处理一个事件。

【讨论】:

  • 是的,但没有提到处理程序队列将再添加一个相同的处理程序函数实例。是设计使然吗?
  • 我不明白the handler queue will be added one more same handler function instance,但 jQuery 将通过设计传递所有过滤的子节点,并在逻辑上将事件处理程序附加到它们。结果,您将处理 2 个事件。尝试通过一些类名来区分这两个内部 div,并将其包含在 on 选择器中,你会看到。
  • handlerQueue.push({ elem: cur, matches: matches }); 可以查看JQuery中dispatch函数的相关源码。
【解决方案2】:

如果您有充分的理由将事件处理程序绑定到 div 而不是 input,则可以将事件处理程序更改为如下所示:

$div.on('validate', '*', function(event) {
    var isRelevant = false;
    $(this).children().each(function(){
        if (this === event.target){
            isRelevant = true;
        }
    });

    if (isRelevant){
        console.log('div');
    }
});

这个 sn-p 检查发出事件的 event.target (doc) 是否是事件传播到的元素的直接子元素之一。 isRelevant 只会对内部 div 为 true。

您也可以在我从您的示例中提取的叉子中查看它:http://jsfiddle.net/BwseN/

【讨论】:

    【解决方案3】:

    $input.blur(function() {$(this).trigger('validate');});'仅输入触发器 验证事件。所以我不认为这意味着“为 DOM 上的每个元素工作 onblur

    这是错误的,因为触发器会在 DOM 树上冒泡,并为父元素触发相同的事件。

    From DOCS

    从 jQuery 1.3 开始,.trigger()ed 事件在 DOM 树中冒泡;事件处理程序可以通过从处理程序返回 false 或在传递给事件的事件对象上调用 .stopPropagation() 方法来停止冒泡

    所以div#container里面有两个div,

    $input.blur(function() {$(this).trigger('validate');});
    

    也触发这两个 div。

    这是一个例子,你可以看到哪个元素是currentTarget,

    http://jsfiddle.net/egcVb/4/

    $div.on('validate', 'div', function(e) {
        console.log(e.currentTarget);
    });
    

    如果您想停止冒泡,只需从该事件中返回 false。

    http://jsfiddle.net/egcVb/5/

    $div.on('validate', 'div', function(e) {
        console.log(e.currentTarget);
        return false;
    });
    

    【讨论】:

    • '$input.blur(function() {$(this).trigger('validate');});'只有输入触发器验证事件。所以我不认为意味着“为 DOM 上的每个元素工作 onblur
    • 实际上div#container中有两个div元素和一个input元素。
    • 您的 jsFiddle 示例中的代码和您在此处编写的代码不同。你参考哪一个?
    • 后者。对此感到抱歉。
    • 您能否指出文档中证明“为父元素触发相同事件”的关键词。谢谢,
    猜你喜欢
    • 1970-01-01
    • 2011-12-12
    • 2014-01-15
    • 2011-01-15
    • 2012-01-26
    • 1970-01-01
    • 1970-01-01
    • 2012-02-20
    • 1970-01-01
    相关资源
    最近更新 更多