内容简介:前言:这篇依旧长,请耐心看下去。
前言:
这篇依旧长,请耐心看下去。
一、事件委托
DOM有个 事件流 特性,所以触发DOM节点的时候,会经历3个阶段:
(1)阶段一:Capturing 事件捕获(从祖到目标)
在 事件
自上(document->html->body->xxx)而下到达目标节点的过程中,浏览器会检测 针对该事件的 监听器(用来捕获事件) ,并运行 捕获事件的监听器 。
(2)阶段二:Target 目标
浏览器找到监听器后,就运行该监听器
(3)阶段三:Bubbling 冒泡(目标到祖)
在 事件
自下而上(document->html->body->xxx)到达目标节点的过程中,浏览器会检测 不是 针对该事件的 监听器(用来捕获事件) ,并运行 非捕获事件的监听器 。
二、 $()
.click()
作用:
为目标元素绑定点击事件
源码:
//这种写法还第一次见,将所有鼠标事件写成字符串再换成数组 //再一一绑定到DOM节点上去 //源码10969行 jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + "change select submit keydown keypress keyup contextmenu" ).split( " " ), function( i, name ) { //事件绑定 // Handle event binding jQuery.fn[ name ] = function( data, fn ) { return arguments.length > 0 ? //如果有参数的话,就用jQuery的on绑定 this.on( name, null, data, fn ) : //否则使用trigger this.trigger( name ); }; } );
解析:
可以看到,jQuery 将所有的鼠标事件都一一列举了出来,并通过 jQuery.fn[ name ] = function( data, fn ) { xxx }
如果有参数,则是绑定事件,调用 on() 方法;
没有参数,则是调用事件,调用 trigger() 方法( trigger() 放到下篇讲 )
三、 $()
.on()
作用:
在被选元素及子元素上添加一个或多个事件处理程序
源码:
//绑定事件的方法 //源码5812行 jQuery.fn.extend( { //在被选元素及子元素上添加一个或多个事件处理程序 //$().on('click',function()=<{}) //源码5817行 on: function( types, selector, data, fn ) { return on( this, types, selector, data, fn ); }, //xxx //xxx })
最终调用的是 jQuery.on() 方法:
//绑定事件的on方法 //源码5143行 //目标元素,类型(click,mouseenter,focusin,xxx),回调函数function(){xxx} function on( elem, types, selector, data, fn, one ) { var origFn, type; //这边可以不看 // Types can be a map of types/handlers if ( typeof types === "object" ) { // ( types-Object, selector, data ) if ( typeof selector !== "string" ) { // ( types-Object, data ) data = data || selector; selector = undefined; } for ( type in types ) { on( elem, type, selector, data, types[ type ], one ); } return elem; } //直接调用$().on()的话会走这边 if ( data == null && fn == null ) { // ( types, fn ) //fn赋值为selector,即function(){} fn = selector; //再将selector置为undefined //注意这个写法,连等赋值 data = selector = undefined; } //调用像$().click()的话会走这边 else if ( fn == null ) { if ( typeof selector === "string" ) { // ( types, selector, fn ) fn = data; data = undefined; } else { // ( types, data, fn ) fn = data; data = selector; selector = undefined; } } if ( fn === false ) { fn = returnFalse; } else if ( !fn ) { return elem; } //one()走这里 if ( one === 1 ) { //将fn赋给origFn后,再定义fn origFn = fn; fn = function( event ) { //将绑定给目标元素的事件传给fn, //并通过$().off()卸载掉 // Can use an empty set, since event contains the info jQuery().off( event ); //在origFn运行一次的基础上,让origFn调用fn方法,arguments即event return origFn.apply( this, arguments ); }; //让fn和origFn使用相同的guid,这样就能移除origFn方法 // Use same guid so caller can remove using origFn fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); } return elem.each( function() { //最终调动$.event.add方法 jQuery.event.add( this, types, fn, data, selector ); } ); }
解析:
可以看到,由于将 bind()、live() 和 delegate() 都合并进 on() 后,on() 里面的情况挺复杂的, data、selector、fn 相互赋值。
注意下 if ( one === 1 )
这种情况,是 $().one()
在 on()
里的具体实现,即调用一次 on()
后,就执行 jQuery().off( event )
,卸载事件。
该方法最终调用 jQuery.event.add( ) 方法
四、jQuery.event.add( )
作用:
为目标元素添加事件
源码:
//源码5235行 /* * Helper functions for managing events -- not part of the public interface. * Props to Dean Edwards' addEvent library for many of the ideas. */ jQuery.event = { global: {}, //源码5241行 //this, types, fn, data, selector add: function( elem, types, handler, data, selector ) { var handleObjIn, eventHandle, tmp, events, t, handleObj, special, handlers, type, namespaces, origType, //elemData正是目标元素jQuery中的id属性 //初始值是{} elemData = dataPriv.get( elem ); // Don't attach events to noData or text/comment nodes (but allow plain objects) if ( !elemData ) { return; } //调用者可以传入一个自定义数据对象来代替处理程序 // Caller can pass in an object of custom data in lieu of the handler if ( handler.handler ) { handleObjIn = handler; handler = handleObjIn.handler; selector = handleObjIn.selector; } //确保不正确的选择器会抛出异常 // Ensure that invalid selectors throw exceptions at attach time // Evaluate against documentElement in case elem is a non-element node (e.g., document) if ( selector ) { jQuery.find.matchesSelector( documentElement, selector ); } //确保handler有唯一的id // Make sure that the handler has a unique ID, used to find/remove it later if ( !handler.guid ) { handler.guid = jQuery.guid++; } //如果事件处理没有,则置为空对象 // Init the element's event structure and main handler, if this is the first //在这里,就应经给events赋值了, // 注意这种写法:赋值的同时,判断 if ( !( events = elemData.events ) ) { events = elemData.events = {}; } if ( !( eventHandle = elemData.handle ) ) { eventHandle = elemData.handle = function( e ) { //当在一个页面卸载后调用事件时,取消jQuery.event.trigger()的第二个事件 // Discard the second event of a jQuery.event.trigger() and // when an event is called after a page has unloaded //jQuery.event.triggered: undefined //e.type: click/mouseout return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? //让elem调用jQuery.event.dispatch方法,参数是arguments jQuery.event.dispatch.apply( elem, arguments ) : undefined; }; } //通过空格将多个events分开,一般为一个,如click // Handle multiple events separated by a space types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; t = types.length; while ( t-- ) { tmp = rtypenamespace.exec( types[ t ] ) || []; //click type = origType = tmp[ 1 ]; //"" namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); // There *must* be a type, no attaching namespace-only handlers if ( !type ) { continue; } //如果event改变了它自己的type,就使用特殊的event handlers // If event changes its type, use the special event handlers for the changed type special = jQuery.event.special[ type ] || {}; //如果选择器已定义,确定一个特殊event api的type //否则使用默认type // If selector defined, determine special event api type, otherwise given type type = ( selector ? special.delegateType : special.bindType ) || type; //不明白为什么在上面要先写一遍 // Update special based on newly reset type special = jQuery.event.special[ type ] || {}; //handleObj会传递给所有的event handlers // handleObj is passed to all event handlers handleObj = jQuery.extend( { type: type, origType: origType, data: data, handler: handler, guid: handler.guid, selector: selector, needsContext: selector && jQuery.expr.match.needsContext.test( selector ), namespace: namespaces.join( "." ) }, handleObjIn ); //第一次绑定事件,走这里 // Init the event handler queue if we're the first if ( !( handlers = events[ type ] ) ) { handlers = events[ type ] = []; handlers.delegateCount = 0; // Only use addEventListener if the special events handler returns false if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { //目标元素有addEventListener的话,调用绑定click事件 if ( elem.addEventListener ) { elem.addEventListener( type, eventHandle ); } } } //special的add/handleObj.handler.guidd的初始化处理 if ( special.add ) { special.add.call( elem, handleObj ); if ( !handleObj.handler.guid ) { handleObj.handler.guid = handler.guid; } } // Add to the element's handler list, delegates in front if ( selector ) { handlers.splice( handlers.delegateCount++, 0, handleObj ); } else { handlers.push( handleObj ); } //一旦有绑定事件,全局通知 // Keep track of which events have ever been used, for event optimization jQuery.event.global[ type ] = true; } }, ... ... }
解析:
可以看到,很多的 if 判断,都是在初始化对象,最后通过 while 循环,调用目标元素的 addEventListener 事件,也就是说,click()/on() 的本质是 element.addEventListener() 事件,前面一系列的铺垫,都是在为目标 jQuery 对象添加必要的属性。
注意写法 if ( !( events = elemData.events ) )
,在赋值的同时,判断条件
(1)dataPriv
//取唯一id //源码4361行 var dataPriv = new Data();
在 jQuery 对象中,有唯一id的属性
$("#one")
elemData = dataPriv.get( elem )
① Data()
//目标元素的jQuery id //源码4209行 function Data() { this.expando = jQuery.expando + Data.uid++; }
② jQuery.expando
jQuery.extend( { //相当于jQuery为每一个元素取唯一的id ///\D/g : 去掉非数字的字符 // Unique for each copy of jQuery on the page //源码360行 expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), ... ... })
③ Math.random()
伪随机,到小数点后16位
expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
可以看到 jQuery 的 id 是由 jQuery + 版本号+ Math.random() 生成的
关于 Math.random() 是如何生成伪随机数的请看: https://www.zhihu.com/question/22818104
(2)rtypenamespace
var rkeyEvent = /^key/, rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, //事件类型的命名空间 //举例:var arr1 = "click.aaa.bbb".match(rtypenamespace); //console.log(arr1);//["click.aaa.bbb", "click", "aaa.bbb", index: 0, input: "click.aaa.bbb"] //源码5131行 rtypenamespace = /^([^.]*)(?:\.(.+)|)/;
综上,绑定事件的本质即调用 element.addEventListener()
方法,但 jQuery 有太多的情况需要考虑了。
(完)
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- vue-源码剖析-双向绑定
- Vue源码之双向数据绑定
- Vue源码解析:双向绑定原理
- Vue源码探究-数据绑定逻辑架构
- Netty源码分析--Channel注册&绑定端口(下)(七)
- Laravel 核心——IoC 服务容器源码解析(服务器绑定)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。