问题1:delegateCount到底是什么鬼,为什么抓着delegateCount不放?
JS部分
$("#father").on("click",".class",function(e)//传入了.class的选择器,说明他代理了.class所有的DOM元素 { console.log("child"); }) $("#father").on("click",".child1",function(e)//传入了.child1,表示它又代理了.child所有的DOM元素,因此father元素的click事件delegateCount是2 { console.log("child"); }) $("#father").on("mouseover",".class",function(e)//传入了.class选择器,表示father元素代理了所有.class的DOM元素,所以mouseover事件的delegateCount是1! { console.log("child"); }) var expando=jQuery.expando; var key=$("#father")[0][expando]; var walhouse=jQuery.cache; var data=walhouse[key]; console.log(data);//仓库里面的数据HTML部分:
<div id="father" style="background-color:red;height:100px;"> <div style="background-color:yellow;height:50px;" class="child"> 我是华为 </div> <div style="background-color:yellow;height:50px;" class="child1"> 我是华为 </div> </div>通过 该图, 我们知道对于father元素的click事件来说,他代理了selector为.child1和.class的所有的DOM元素,所以click事件的delegateCount是2;对于father的mouseover事件来说,他代理了.class的元素的所有的DOM元素,所以mouseover事件的delegateCount为1!但是通用的handle的elem属性一直就是father元素!
对于jQuery.event.dispatch是如何被调用的
jQuery.event.dispatch.apply( eventHandle.elem, arguments ) ://dispatch上下文就是代理对象,也就是上面例子中的father!jQuery.event.dispatch中调用了jQuery.event.handlers方法
handlerQueue = jQuery.event.handlers.call( this, event, handlers );//所以handlers上下文也就是代理对象!note:在dispatch方法中上下文是元素的通用函数的elem属性,也就是代理对象即绑定事件的对象, 而handlers在dispatch中调用,所以他的this上下文也就是elem即 代理对象
解答:他的作用在于通过把代理对象中的某一类事件组成的handleObj数组进行遍历。当事件触发的时候,从target开始,一直到代理对象,看看每一个对象是否符合这个handleObj中某一个元素的selector,如果符合那么就保存。所以,对于每一个DOM对象,都要循环遍历handleObj数组,看看该DOM对象是否符合该handleObj的选择器!
很显然是双重for循环:
for ( ; cur != this; cur = cur.parentNode || this ) { /* jshint eqeqeq: true */ // Don't check non-elements (#13208) // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) { matches = []; //获取该类型事件代理的选择器的个数,如click代理了.class和.child1 for ( i = 0; i < delegateCount; i++ ) { //获取代理类型的handleObj,每代理一次都会有一个handleObj对象 handleObj = handlers[ i ]; // Don't conflict with Object.prototype properties (#13203) sel = handleObj.selector + " "; if ( matches[ sel ] === undefined ) { //如果代理的这个选择器的DOM元素的needsContext是false //那么在代理对象下面通过代理时候的选择器再次筛选,也就是筛选出来当前代理对象代理了那些子元素的同类事件! //如果needsContext是true,那么看看当前元素是否符合被代理的要求! matches[ sel ] = handleObj.needsContext ? jQuery( sel, this ).index( cur ) >= 0 : jQuery.find( sel, this, null, [ cur ] ).length; } //如果当前元素符合被代理的要求,那么把handleObj放入数组,准备返回! if ( matches[ sel ] ) { matches.push( handleObj ); } } //返回的数据类型是DOM+满足的回调函数的集合(是一个数组) if ( matches.length ) { handlerQueue.push({ elem: cur, handlers: matches }); } }问题4:怎么会出现delegateCount<handlers.length?
if ( delegateCount < handlers.length ) { handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });//这些事件是直接绑定到代理对象自身的,仅仅给代理对象 }我们要知道,一个DOM可以代理子元素的事件,但是也能够仅仅为自己绑定事件,这时候就会出现这种情况,见下面的例子:
$("#father").on("click",".class",function(e)//代理 { console.log("child"); }) $("#father").click(function()//非代理,这个事件是直接给自己的 { console.log("father"); }); $("#father").on("click",".child1",function(e)//代理 { console.log("child"); }) $("#father").on("mouseover",".class",function(e)//代理 { console.log("child"); }) var expando=jQuery.expando; var key=$("#father")[0][expando]; var walhouse=jQuery.cache; var data=walhouse[key]; console.log(data);//仓库里面的数据通过 该图你会发现,对于click来说,他的delegateCount是2,但是却绑定了3个click事件,最后一个事件不是代理的,而是为自己的click事件绑定的函数。所以,对于非代理的事件我们直接slice获取delegateCount剩余的handleObj就可以了!
handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });很显然 ,返回的是对象数组,每一个对象的第一个维度是DOM,第二个维度是该DOM满足了那些handleObj具有的选择器。换句话说,一个DOM可能满足多个handleObj具有的选择器,所以他的回调函数是一个数组!
<div style="background-color:yellow;height:50px;" class="child child1"> 我是华为 </div>JS部分
$("#father").on("click",".child",function(e)//代理 { console.log("child"); }) $("#father").click(function()//非代理 { console.log("father"); }); $("#father").on("click",".child1",function(e)//代理 { console.log("child"); })这时候对于div元素来说,他满足了.child1和.child两个选择器。在father具有的3个handleObj中, 该div元素满足其中handleObj个选择器,所以该elem具有的matches数组中存放了两个handleObj!
解答:不需要返回
if ( matches.length ) {//如果该DOM不满足任何一个handleObj的selector,那么我们不返回该DOM! handlerQueue.push({ elem: cur, handlers: matches }); }问题7:handleObj中的needsContext是什么鸟?
needsContext: selector && jQuery.expr.match.needsContext.test( selector )//我们看看该正则表达式是什么jQuery.expr.match.needsContext正则表达式
/^[\x20\t\r\n\f]*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\([\x20\t\r\n\f]*((?:-\d)?\d*)[\x20\t\r\n\f]*\)|)(?=[^-]|$)/i表明,像even,odd等选择器才会满足这个正则表达式,传入着这种selector那么才表明需要上下文
那么如果用户传入了这种选择器,那么我们怎么处理
matches[ sel ] = handleObj.needsContext ? jQuery( sel, this ).index( cur ) >= 0 :jQuery.find( sel, this, null, [ cur ] ).length;也就是说, 如果传入了这种选择器,那么我们不过是在判断当前事件流中的DOM元素是否符合这个选择器而已!如果不是这种选择器,我们就用Sizzle来选择,但是 目地也只有一个就是判断当前DOM是否满足该handleObj的选择器,如果满足才会把该handleObj当作该DOM的!
if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) {表示代理了,同时当前事件流所在的元素是Element元素,同时鼠标按键是左键或者非click类型才会受理!注意:左键表示0,1表示中间键,2表示右键!
handlers: function( event, handlers ) { var sel, handleObj, matches, i, handlerQueue = [], //记录一下当前DOM代理了多少个同类事件! delegateCount = handlers.delegateCount, //获取冒泡时当前的元素 cur = event.target; // Find delegate handlers // Black-hole SVG <use> instance trees (#13180) // Avoid non-left-click bubbling in Firefox (#3861) if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { /* jshint eqeqeq: false */ //如果当前DOM不是调用绑定的对象,我们知道dispatch里面的上下文是注册事件的那个元素,而不是发生事件的元素 //所以,他代理的元素的事件都会冒泡到代理元素上面进行处理。但是,我们应该要知道,为了能在代理的对象的事件 //处理函数中获取到被代理的所有的事件处理函数,我们需要保存所有的被代理对象的事件处理函数 for ( ; cur != this; cur = cur.parentNode || this ) { /* jshint eqeqeq: true */ // Don't check non-elements (#13208) // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) { matches = []; //获取该类型事件代理的选择器的个数,如click代理了.class和.child1 for ( i = 0; i < delegateCount; i++ ) { //获取代理类型的handleObj,每代理一次都会有一个handleObj对象 handleObj = handlers[ i ]; // Don't conflict with Object.prototype properties (#13203) sel = handleObj.selector + " "; if ( matches[ sel ] === undefined ) { //如果代理的这个选择器的DOM元素的needsContext是false //那么在代理对象下面通过代理时候的选择器再次筛选,也就是筛选出来当前代理对象代理了那些子元素的同类事件! //如果needsContext是true,那么看看当前元素是否符合被代理的要求! matches[ sel ] = handleObj.needsContext ? jQuery( sel, this ).index( cur ) >= 0 : jQuery.find( sel, this, null, [ cur ] ).length; } //如果当前元素符合被代理的要求,那么把handleObj放入数组,准备返回! if ( matches[ sel ] ) { matches.push( handleObj ); } } //返回的数据类型是DOM+满足的回调函数的集合(是一个数组) if ( matches.length ) { handlerQueue.push({ elem: cur, handlers: matches }); } } } } //执行到这里,表明for循环中cur==this了!但是有些事件不是代理的,而是自己给自己绑定的,这时候就会出现 //delegateCount< handlers.length的情况!这时候直接把剩余的函数直接以this,也就是代理元素为键返回! // Add the remaining (directly-bound) handlers if ( delegateCount < handlers.length ) { handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); } return handlerQueue; }
总结:
该方法的处理对象是:代理对象某一类事件所有的handleObj的数组!见该图
该方法的处理时机是:从目标对象向上冒泡到代理对象的过程中!