提到jQuery的事件,不得不提一下 Dean Edwards大神 addEvent 库,很多流行的类库的基本思想从他那儿借来的。jQuery的事件处理机制吸取了 JavaScript 专家 Dean Edwards 编写的事件处理函数的精华,使得jQuery处理事件绑定的时候相当的可靠。在预留退路(graceful degradation),循序渐进以及非入侵式编程思想方面,jQuery 也做的非常不错。总的来说对于 jQuery 的事件绑定做了 2 大块的处理:
在绑定的时候做了包装处理
在执行的时候有过滤器处理
看看 API 的参数:
.on( events [, selector ] [, data ], handler(eventObject) )
用来绑定一个事件:
var body = $('body') body.on('click','p',function(){ console.log(this) })
用 on 方法给 body 上绑定一个 click 事件,冒泡到 p 元素的时候才出发回调函数,这里大家需要明确一点:
每次在body上点击其实都会触发事件,但是只目标为p元素的情况下才会触发回调的处理函数。通过源码不难发现on方法实质只完成一些参数调整的工作,而实际负责事件绑定的是其内部 jQuery.event.add方法
看看绑定的实际接口on的代码:
on: function(types, selector, data, fn, /*INTERNAL*/ one) { //省略部分代码 return this.each(function() { jQuery.event.add(this, types, fn, data, selector); }); }
jQuery.event.add内部实际上最终还是通过addEventListener绑定的事件
(单击图片可放大)
其中一些变量代码的意思:
现在我们把之前的案例给套一下看看:
var body = document.getElementsByTagName('body') var eventHandle = function() { console.log(this) } body.addEventListener('click', eventHandle, false);
如果是我们自己实现的这个代码是有问题的,我们在body上每次都触发了click事件,但是我们并没有委托的p元素的处理,自然也达不到委托的效果。
eventHandle源码
回到内部绑定的事件句柄 eventHandle ,可想而知 eventHandle 不仅仅只是只是充当一个回调函数的角色,而是一个实现了 EventListener 接口的对象。
if (!(eventHandle = elemData.handle)) { eventHandle = elemData.handle = function(e) { return typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ? jQuery.event.dispatch.apply(elem, arguments) : undefined; }; }
可见在 eventHandle 中并没有直接处理回调函数,而是映射到 jQuery.event.dispatch 分派事件处理函数了仅仅只是传入 eventHandle.elem,arguments , 就是 body 元素 与事件对象那么这里有个问题,事件回调的句柄并没有传递过去,后面的代码如何关联?本章的一些地方可能要结合后面的 dispatch 处理才能清理,但是我们还是先看看做了那些处理。
一个简单的流程图:
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <script src="http://www.imooc.com/static/lib/jquery/1.9.1/jquery.js" type="text/javascript"></script> <title>模拟jQuery</title> </head> <body> <p>模拟jQuery的整个事件流程绑定到执行</p> <p>包括</p> <p>1. 通过on绑定</p> <p>2. 通过dispatch派发</p> <p>3. 通过fix修正事件对象</p> <p>当然这里还没有涉及最重要的一点事件的委托过滤机制,考虑代码太复杂了,后面会有分析</p> <p id="aarontest" style="color:red">点击执行</p> <script type="text/javascript"> //事件缓存 //用来代替jquery的data缓存 var eventCache = {}; var $$ = ajQuery = function(selector) { return new ajQuery.fn.init(selector); } ajQuery.fn = ajQuery.prototype = { name: 'aaron', init: function(selector) { this.selector = selector; this[0] = document.querySelectorAll(selector)[0] return this; }, constructor: ajQuery } ajQuery.fn.init.prototype = ajQuery.fn ajQuery.event = { //增加事件 add:function(elem, types, handler){ var eventHandle = function(e) { return ajQuery.event.dispatch.apply(elem, arguments) }; //把用户的回调,放到缓存中 eventCache['handler'] = handler if (elem.addEventListener) { elem.addEventListener(types, eventHandle, false); } }, //派发事件 dispatch:function(event){ //修正事件对象 event = ajQuery.event.fix(event); //var handlerQueue = ajQuery.event.handlers.call(this, event, eventCache['handler']); eventCache['handler'].call(this,event) }, //执行事件句柄 handlers: function(event, handlers) { var handlerQueue = []; handlerQueue.push({ elem: this, handlers: handlers.slice(delegateCount) }); }, //修正事件对象 fix:function(event){ //原始事件引用 var originalEvent = event; //鼠标事件对象的属性 var props= "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "); //创建事件对象 var event = new ajQuery.Event(originalEvent); //混入事件属性 i = props.length; while (i--) { prop = props[i]; event[prop] = originalEvent[prop]; } return event } } function returnTrue() { return true; } function returnFalse() { return false; } //模拟出事件对象 //增加调用判断方法 ajQuery.Event = function(src, props) { if (src && src.type) { this.originalEvent = src; this.type = src.type; this.isDefaultPrevented = src.defaultPrevented || src.defaultPrevented === undefined && src.returnValue === false ? returnTrue : returnFalse; } else { this.type = src; } this.timeStamp = src && src.timeStamp || jQuery.now(); this[jQuery.expando] = true; }; ajQuery.Event.prototype = { isDefaultPrevented: returnFalse, isPropagationStopped: returnFalse, isImmediatePropagationStopped: returnFalse, preventDefault: function() { var e = this.originalEvent; this.isDefaultPrevented = returnTrue; if (e && e.preventDefault) { e.preventDefault(); } }, stopPropagation: function() { var e = this.originalEvent; this.isPropagationStopped = returnTrue; if (e && e.stopPropagation) { e.stopPropagation(); } }, stopImmediatePropagation: function() { var e = this.originalEvent; this.isImmediatePropagationStopped = returnTrue; if (e && e.stopImmediatePropagation) { e.stopImmediatePropagation(); } this.stopPropagation(); } }; ajQuery.fn.on = function(types, fn) { //参数过滤 //............... ajQuery.event.add(this[0], types, fn); } $$('#aarontest').on('click',function(){ alert('通过click绑定,慕课网') }) </script> </body> </html>