开发中遇到的jQuery 事件处理机制的问题

原创
2013/11/27 13:15
阅读数 1.3K

前段时间遇到dom绑定click事件,触发时会重复执行绑定的函数,探究下jQuery事件绑定机制。基于jQuery-1.10.2。

###起因:(对原始问题的一个抽象案例)

<!-- lang: html -->
<html>
<head>
    <title>test click</title>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js">    </script>
</head>
<body>

<a href="#" id="click1" class="clickc">click1</a> <br>
<a href="#" id="click2" class="clickc">click2</a> <br>
<a href="#" id="click3" class="clickc">click3</a> <br>
<a href="#" id="click4" class="clickc">click4</a> <br>
<a href="#" id="click5" class="clickc">click5</a> <br>

<hr>
<a href="#" id="click" class="target">click</a>

<script type="text/javascript">
var count = 0;
function clickEvent()
{
    console.log("click --" + count);
    console.log("---------------");
    count ++;
}
// $('#click1').click(function(){
//     $("#click").click(clickEvent);
// })
// $('#click2').click(function(){
//     $("#click").click(clickEvent);
// })
// $('#click3').click(function(){
//     $("#click").click(clickEvent);
// })
// $('#click4').click(function(){
//     $("#click").click(clickEvent);
// })
// $('#click5').click(function(){
//     $("#click").click(clickEvent);
// })

$('.clickc').click(function(){
    //$('.target').off();
    $('.target').click(clickEvent);
    // console.log(count);
    // count++;

    // var c = document.getElementById('click');
    // c.addEventListener('click', clickEvent);

})
$('.clickclass').one('click', clickEvent);
</script>
</body>
</html>

####问题描述

  1. 点击click[1-5]中的任意一个,接着点击最下面的 click, 这时会在控制台输出,此时clickEvent只执行一次【F12, 打开chrome调试工具,找到console】
  2. 点击click[1-5]中的任意一个,此时点击最下面的 click,此时在console会有2个输出,说明 clickEvent 被执行了2 次
  3. 如果不刷新,一直重复上面 的操作,最终的结果就是 点击几次click[1-5],点击click时就会执行几次clickEvent函数

###原因分析 jQuery的事件绑定机制里用数组来保存事件,如果对同一元素进行重复绑定,不会覆盖之前已经绑定的事件,只是把新的绑定事件再push到保存事件的数组中,当事件触发时就会循环执行数组中的事件。

####jQuery源码中add函数进行元素事件的绑定。

<!-- lang: js -->
add: function( elem, types, handler, data, selector ) {
    ......
    if ( !(handlers = events[ type ]) ) {
    handlers = events[ type ] = []; //事件处理队列
        ....
    }
    ......
    // 添加到事件列表中, 
if ( selector ) {
    handlers.splice( handlers.delegateCount++, 0, handleObj );
} else {
    handlers.push( handleObj );
}
    ......
}

####jQuery源码中dispatch进行事件处理

<!-- lang: js -->
dispatch: function( event ) {
    ....
    handlerQueue = jQuery.event.handlers.call( this, event, handlers );//拿到事件队列
    i = 0;
    while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
       event.currentTarget = matched.elem;
        j = 0;
        while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {
    // Triggered event must either 1) have no namespace, or
    // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
            if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {

                event.handleObj = handleObj;
                event.data = handleObj.data;

        ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
                .apply( matched.elem, args );
        ....
            }
        }
    }
    return event.result;
}

在通过$("").click()$("").on("click",function)进行事件注册时,都会调用add进行事件注册。

###解决方法 通过以上分析可以知道,如果对同一元素进行多次事件绑定,当触发事件的时候就会执行事件队列里的所有的处理函数。因此可以有以下几种方法解决:

  1. 对元素只进行一次事件绑定。

  2. 对元素进行事件绑定前,先调用off()方法,解决该元素的所有绑定事件处理函数。

    <!-- lang: js -->

    $(elem).off(); $(elem).click(func);

  3. 使用原生js进行事件注册,原生的js对事件没有缓存机制,进行多次绑定只有最后一次是有效的,后面的会覆盖前面的事件处理函数。

    <!-- lang: js -->

    var elem = document.getElementById('click'); elem.addEventListener('click', func);

###【补充】Learning jQuery, 4th Edition p77 在jQuery中,当一个处理函数绑定到事件上时,之前绑定的处理函数依然有效。可以通过调用 .off()方法一次性解除所有的绑定。

p78 如果想在事件处理函数第一次触发后就会解除绑定,可以用.one()来绑定函数。

.one()来绑定函数,事件处理函数会接着执行。

<!-- lang: js -->
one: function( types, selector, data, fn ) {
return this.on( types, selector, data, fn, 1 );
}
on: function( types, selector, data, fn, /*INTERNAL*/ one ){
    ....
    if ( one === 1 ) {
    origFn = fn;
    fn = function( event ) {
        // Can use an empty set, since event contains the info
        jQuery().off( event );
        return origFn.apply( this, arguments ); //直接调用并返回
    };
    // Use same guid so caller can remove using origFn
    fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
}
return this.each( function() {
	jQuery.event.add( this, types, fn, data, selector );
});
}
展开阅读全文
加载中
点击加入讨论🔥(9) 发布并加入讨论🔥
打赏
9 评论
18 收藏
2
分享
返回顶部
顶部