微信号:FrontDev

介绍:分享 Web 前端相关的技术文章、工具资源、精选课程、热点资讯

前端事件系统(三)

2015-10-30 19:57 前端大全

(点击上方,可快速关注)


作者:observernote

网址:http://www.cnblogs.com/observernotes/p/4827956.html


上一章对于jQuery的事件系统,对于jQuery的一些事件绑定接口做了分析。同时,引入了事件委托的概念。那么,从本章起,将开始深入到jQuery的事件系统内部,对于其源码进行解析。


这一篇是可以独自拿出来看,与前面两章虽然有些关系,但是如果只是对于jQuery源码有兴趣的,并且对前端事件有些理解的,从这章开始看也是可以的。


前端事件系统(一)

前端事件系统(二)


on方法


上一章提及到jQuery绑定的核心,是落在了on方法身上的。on方法相当于对于jQuery的事件接口进行了统一,让jQuery其他事件绑定有关的方法,直接调用on方法来进行实现。因此,我们将拿on方法作为jQuery事件一开始的一个突破口,来对jQuery事件进行层层分析。下面来看jQuery的on方法


on : function( types, selector, data, fn, /*INTERNAL*/ one ) {

var origFn, type;


// Types can be a map of types/handlers

/*

* 第一个参数传入为对象的处理,类似

* {“event1name”:function(){},

* "event2name":function(){}

* }

* 这样的写法。

* 然后调用自身on方法,分别对其进行on方法绑定事件

*/

if ( typeof types === "object" ) {

// ( types-Object, selector, data )

if ( typeof selector !== "string" ) {

// ( types-Object, data )

data = data || selector;

selector = undefined;

}

for ( type in types ) {

this.on( type, selector, data, types[ type ], one );

}

return this;

}


//对于data与fn为null的情况下的处理

if ( data == null && fn == null ) {

// ( types, fn )

fn = selector;

data = selector = undefined;

//对于仅仅fn为null的情况下的处理

} 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;

}

}

//如果执行函数直接是false的情况的处理returnFalse即是直接return False的函数

if ( fn === false ) {

fn = returnFalse;

} else if ( !fn ) {

return this;

}


//如果最后一个参数为1,那么直接调用fn,并解绑。

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++ );

}

//直接调用jQuery.event.add并传入处理好的参数

return this.each( function() {

jQuery.event.add( this, types, fn, data, selector );

});

}


可以发现,作为绑定核心的on方法,其实质上只是对传进来的参数进行调整处理,并将处理过后的参数,传入jQuery.event.add中来进行处理,因此,接下来我们将对jQuery.event.add的源码来进行分析与阅读。


jQuery.event.add方法


这个方法比较长,并且内部有许多东西,暂时还不好去读,因此,我将这一部分分为几个部分来进行阅读。为了方便理解,我将其分成几个部分,然后一一来解读。我尽量做到对这一部分的讲解做到简单易懂,解读过程中有不对的地方,还请指出。同时,这一部分的代码,除非特别说明,否则全部来自于jQuery.event.add。


绑定部分


首先,我们先直接跳到事件注册那一部分,即addeventListener,看看他做了些什么


// 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

// 如果获取特殊事件失败,那么采用addEventListener来进行事件注册

if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {

if ( elem.addEventListener ) {

// 注册时将eventHandle来进行注册,并且仅执行一次

elem.addEventListener( type,eventHandle,false );

}

}

}


这段代码,实际就是对于handlers的初始化,同时,对事件采用addEventListenr进行了绑定。


在这一段代码中,我们可以看出,jQuery在绑定事件时,与以下几个部分有关:


  • eventHandle

  • events

  • handlers

  • special


下面将对这几个部分,以及这几个部分衍生出来的部分一一进行解读。同时,以下几部分代码,为了方便阅读,对本身这部分的代码进行了一些抽象(这词用到这里怎么怪怪的),方便于理解


eventHandle部分


//获取数据缓存

var elemData = data_priv.get( elem );

...

// 如果elemData没有handle属性,给其初始化一个handle属性

if ( !(eventHandle = elemData.handle) ) {

eventHandle = elemData.handle = function( e ) {

// Discard the second event of a jQuery.event.trigger() and

// when an event is called after a page has unloaded

return typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ?

jQuery.event.dispatch.apply( elem, arguments ) : undefined;

};

}


这里首先对于我们要操作的结点下的数据缓存进行了获取,同时,将数据缓存下的handle属性,赋给了eventHandle。这里可以发现,elemData.handle这一部分,并没有直接对于事件的回调函数进行处理,而是进一步的使用jQuery.event.dispatch来进行的处理。


jQuery.event.dispatch这里还不会进行阅读,我们只要知道,jQuery对于回调的处理,和jQuery.event.dispatch这部分有关就行了。


events以及handlers部分


var //获取数据缓存

elemData = data_priv.get( elem ),

events;

...

// Init the element's event structure and main handler, if this is the first

// 如果elemData没有events属性,给其初始化一个events属性

if ( !(events = elemData.events) ) {

events = elemData.events = {};

}


events部分,来自于要操作节点下的数据缓存中的events属性。而根据之前的操作


// Init the event handler queue if we're the first

// 如果是第一次,那么初始化事件队列

if ( !(handlers = events[ type ]) ) {

handlers = events[ type ] = [];

...

}


可以明白,events即是对于操作节点下各种事件的类型进行的记录。


handlers部分


var handlers;

...

// Init the event handler queue if we're the first

// 如果是第一次,那么初始化事件队列

if ( !(handlers = events[ type ]) ) {

handlers = events[ type ] = [];

handlers.delegateCount = 0;

...

}

...

// Add to the element's handler list, delegates in front

// 将事件处理的对象加入处理列表

if ( selector ) {

handlers.splice( handlers.delegateCount++, 0, handleObj );

} else {

handlers.push( handleObj );

}


handlers其实是作为一个列表的作用,在里面存放了handleObj的有关内容。


具体存入了什么,我们接下来来看handleObj具体是什么。


handleObj部分


var handleObjIn,handleObj;

...

// Caller can pass in an object of custom data in lieu of the handler

// 对于像{handler:function(){},...}这样的处理(即就是下面的handleObj的格式),后面可用于对handleObj的扩展

if ( handler.handler ) {

handleObjIn = handler;

handler = handleObjIn.handler;

selector = handleObjIn.selector;

}

...

while ( t-- ) {

...

// handleObj is passed to all event handlers

// handleObj是作为对事件处理的对象

// 扩展handleObj的属性

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 );

...

// Add to the element's handler list, delegates in front

// 将事件处理的对象加入处理列表

if ( selector ) {

handlers.splice( handlers.delegateCount++, 0, handleObj );

} else {

handlers.push( handleObj );

}

...

}


handleObj即就是对于事件处理的一个对象,其包含了事件类型,初始类型,事件id,命名空间,dom元素,回调函数等属性。


所以回到之前的handlers,其放入的内容,也就是事件处理对象。


那么到了这里,我们对于jQuery.event.add方法已经有了一些头绪了。那么我们来整理下到目前为止的流程


到这里,add部分做了些什么,我们大概都清楚了。


再回到之前的代码上,我们可以看到,其中的一部分是被while循环所包裹的,那这一部分是实现的什么样的内容呢?


多事件绑定与命名空间的处理


// Handle multiple events separated by a space

// rnotwhite = (/\S+/g)

// 将空格分开的types变为数组

types = ( types || "" ).match( rnotwhite ) || [ "" ];


// 获取事件类型的长度

t = types.length;

while ( t-- ) {

//rtypenamespace = /^([^.]*)(?:\.(.+)|)$/;

//对事件命名空间进行分割与获取

//例:click.myPlugin.simple

tmp = rtypenamespace.exec( types[t] ) || [];

//type则为click

type = origType = tmp[1];

//namespaces则为['myPlugin','simple']

namespaces = ( tmp[2] || "" ).split( "." ).sort();

...

}


这里,对于多事件(类似于(“mouseover click”))进行了处理,并且通过while循环分别对其进行事件注册。同时,while循环中对于事件的命名空间也进行了处理。


special部分


其实之前已经将这个列举了出来,不过因为其并不影响之前的对于整个的阅读,因此,将其拿到最后来说明。


var special;

...

while( t-- ) {

...


// If event changes its type, use the special event handlers for the changed type

// 如果事件改变了其type,采用特殊事件处理

special = jQuery.event.special[ type ] || {};


// If selector defined, determine special event api type, otherwise given type

// 如果selector定义了,就采用special事件的type处理,否则直接采用type

type = ( selector ? special.delegateType : special.bindType ) || type;


// Update special based on newly reset type

// 前面type重写了,因此对special进行一次update

special = jQuery.event.special[ type ] || {};

...

// 特殊事件使用add对其进行处理

if ( special.add ) {

special.add.call( elem, handleObj );


if ( !handleObj.handler.guid ) {

handleObj.handler.guid = handler.guid;

}

}

...

}


因为浏览器兼容性的问题,对于一些事件并不能兼容。因此,对于这些事件,jQuery采用了special event的机制来对一些特殊的事件来进行处理,以达到跨浏览器支持事件的功能。


结语


距离上次写第二篇到现在也好久了,人还是太懒了,写的也很慢。这一章对于on与event.add方法进行了解读,下一章将对于这一章所没讲到的事件委派来进行解析,嗯,还是因为人懒,时间未知。




前端大全

微信号:FrontDev

打造东半球最好的 前端技术 微信号

--------------------------------------

商务合作QQ:2302462408

投稿网址:top.jobbole.com

 
前端大全 更多文章 5个典型的JavaScript面试题(上) Limu:JavaScript的那些书 Web开发:我希望得到的编程学习路线图 JavaScript基础工具清单 常用排序算法之JavaScript实现
猜您喜欢 mnv*框架时代 你愿意做一匹狼还是一只哈士奇 金字塔原理 2015年有用的16大免费响应式HTML5框架 iOS完全自学手册——[三]Objective-C语言速成,利用Objective-C创建自己的对象