前段时间遇到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>
####问题描述
- 点击click[1-5]中的任意一个,接着点击最下面的 click, 这时会在控制台输出,此时
clickEvent
只执行一次【F12, 打开chrome调试工具,找到console】 - 点击click[1-5]中的任意一个,此时点击最下面的 click,此时在console会有2个输出,说明
clickEvent
被执行了2 次 - 如果不刷新,一直重复上面 的操作,最终的结果就是 点击几次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
进行事件注册。
###解决方法 通过以上分析可以知道,如果对同一元素进行多次事件绑定,当触发事件的时候就会执行事件队列里的所有的处理函数。因此可以有以下几种方法解决:
-
对元素只进行一次事件绑定。
-
对元素进行事件绑定前,先调用
off()
方法,解决该元素的所有绑定事件处理函数。<!-- lang: js -->
$(elem).off(); $(elem).click(func);
-
使用原生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 );
});
}