前段时间遇到dom绑定click事件,触发时会重复执行绑定的函数,探究下jQuery事件绑定机制。基于jQuery-1.10.2。javascript
###原由:(对原始问题的一个抽象案例)html
<!-- 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>
####问题描述java
clickEvent
只执行一次【F12, 打开chrome调试工具,找到console】clickEvent
被执行了2 次clickEvent
函数###缘由分析 jQuery的事件绑定机制里用数组来保存事件,若是对同一元素进行重复绑定,不会覆盖以前已经绑定的事件,只是把新的绑定事件再push到保存事件的数组中,当事件触发时就会循环执行数组中的事件。jquery
####jQuery源码中add
函数进行元素事件的绑定。ajax
<!-- 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
进行事件处理chrome
<!-- 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
进行事件注册。api
###解决方法 经过以上分析能够知道,若是对同一元素进行屡次事件绑定,当触发事件的时候就会执行事件队列里的全部的处理函数。所以能够有如下几种方法解决:数组
对元素只进行一次事件绑定。缓存
对元素进行事件绑定前,先调用off()
方法,解决该元素的全部绑定事件处理函数。app
<!-- 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 ); }); }