5-5 绑定设计
本节编程练习不计算学习进度,请电脑登录imooc.com操作

绑定设计

提到jQuery的事件,不得不提一下 Dean Edwards大神 addEvent 库,很多流行的类库的基本思想从他那儿借来的。jQuery的事件处理机制吸取了 JavaScript 专家 Dean Edwards 编写的事件处理函数的精华,使得jQuery处理事件绑定的时候相当的可靠。在预留退路(graceful degradation),循序渐进以及非入侵式编程思想方面,jQuery 也做的非常不错。总的来说对于 jQuery 的事件绑定做了 2 大块的处理:

在绑定的时候做了包装处理
在执行的时候有过滤器处理

看看 API 的参数:

.on( events [, selector ] [, data ], handler(eventObject) )
  1. events:事件名
  2. selector : 一个选择器字符串,用于过滤出被选中的元素中能触发事件的后代元素
  3. data :当一个事件被触发时,要传递给事件处理函数的
  4. handler : 事件被触发时,执行的函数

用来绑定一个事件:

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绑定的事件

(单击图片可放大)

其中一些变量代码的意思:

  1. elem: 目标元素
  2. type: 事件类型,如’click’
  3. eventHandle: 事件句柄,也就是事件回调处理的内容了
  4. false: 冒泡
  5. elem: 目标元素type: 事件类型,如’click’eventHandle: 事件句柄,也就是事件回调处理的内容了false: 冒泡

现在我们把之前的案例给套一下看看:

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 处理才能清理,但是我们还是先看看做了那些处理。

一个简单的流程图:

 

任务

  1. <!DOCTYPE HTML>
  2. <html>
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  5. <script src="http://www.imooc.com/static/lib/jquery/1.9.1/jquery.js" type="text/javascript"></script>
  6. <title>模拟jQuery</title>
  7. </head>
  8. <body>
  9.  
  10. <p>模拟jQuery的整个事件流程绑定到执行</p>
  11. <p>包括</p>
  12. <p>1. 通过on绑定</p>
  13. <p>2. 通过dispatch派发</p>
  14. <p>3. 通过fix修正事件对象</p>
  15. <p>当然这里还没有涉及最重要的一点事件的委托过滤机制,考虑代码太复杂了,后面会有分析</p>
  16. <p id="aarontest" style="color:red">点击执行</p>
  17.  
  18.  
  19. <script type="text/javascript">
  20.  
  21. //事件缓存
  22. //用来代替jquery的data缓存
  23. var eventCache = {};
  24.  
  25. var $$ = ajQuery = function(selector) {
  26. return new ajQuery.fn.init(selector);
  27. }
  28. ajQuery.fn = ajQuery.prototype = {
  29. name: 'aaron',
  30. init: function(selector) {
  31. this.selector = selector;
  32. this[0] = document.querySelectorAll(selector)[0]
  33. return this;
  34. },
  35. constructor: ajQuery
  36. }
  37. ajQuery.fn.init.prototype = ajQuery.fn
  38.  
  39. ajQuery.event = {
  40. //增加事件
  41. add:function(elem, types, handler){
  42. var eventHandle = function(e) {
  43. return ajQuery.event.dispatch.apply(elem, arguments)
  44. };
  45. //把用户的回调,放到缓存中
  46. eventCache['handler'] = handler
  47. if (elem.addEventListener) {
  48. elem.addEventListener(types, eventHandle, false);
  49. }
  50. },
  51.  
  52. //派发事件
  53. dispatch:function(event){
  54. //修正事件对象
  55. event = ajQuery.event.fix(event);
  56. //var handlerQueue = ajQuery.event.handlers.call(this, event, eventCache['handler']);
  57. eventCache['handler'].call(this,event)
  58. },
  59.  
  60. //执行事件句柄
  61. handlers: function(event, handlers) {
  62. var handlerQueue = [];
  63. handlerQueue.push({
  64. elem: this,
  65. handlers: handlers.slice(delegateCount)
  66. });
  67. },
  68. //修正事件对象
  69. fix:function(event){
  70. //原始事件引用
  71. var originalEvent = event;
  72. //鼠标事件对象的属性
  73. 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(" ");
  74.  
  75. //创建事件对象
  76. var event = new ajQuery.Event(originalEvent);
  77.  
  78. //混入事件属性
  79. i = props.length;
  80. while (i--) {
  81. prop = props[i];
  82. event[prop] = originalEvent[prop];
  83. }
  84. return event
  85. }
  86. }
  87.  
  88. function returnTrue() {
  89. return true;
  90. }
  91.  
  92. function returnFalse() {
  93. return false;
  94. }
  95.  
  96. //模拟出事件对象
  97. //增加调用判断方法
  98. ajQuery.Event = function(src, props) {
  99. if (src && src.type) {
  100. this.originalEvent = src;
  101. this.type = src.type;
  102. this.isDefaultPrevented = src.defaultPrevented ||
  103. src.defaultPrevented === undefined &&
  104. src.returnValue === false ?
  105. returnTrue :
  106. returnFalse;
  107. } else {
  108. this.type = src;
  109. }
  110. this.timeStamp = src && src.timeStamp || jQuery.now();
  111. this[jQuery.expando] = true;
  112. };
  113.  
  114. ajQuery.Event.prototype = {
  115. isDefaultPrevented: returnFalse,
  116. isPropagationStopped: returnFalse,
  117. isImmediatePropagationStopped: returnFalse,
  118. preventDefault: function() {
  119. var e = this.originalEvent;
  120. this.isDefaultPrevented = returnTrue;
  121. if (e && e.preventDefault) {
  122. e.preventDefault();
  123. }
  124. },
  125. stopPropagation: function() {
  126. var e = this.originalEvent;
  127. this.isPropagationStopped = returnTrue;
  128. if (e && e.stopPropagation) {
  129. e.stopPropagation();
  130. }
  131. },
  132. stopImmediatePropagation: function() {
  133. var e = this.originalEvent;
  134. this.isImmediatePropagationStopped = returnTrue;
  135. if (e && e.stopImmediatePropagation) {
  136. e.stopImmediatePropagation();
  137. }
  138. this.stopPropagation();
  139. }
  140. };
  141.  
  142.  
  143. ajQuery.fn.on = function(types, fn) {
  144. //参数过滤
  145. //...............
  146. ajQuery.event.add(this[0], types, fn);
  147. }
  148.  
  149.  
  150. $$('#aarontest').on('click',function(){
  151. alert('通过click绑定,慕课网')
  152. })
  153.  
  154.  
  155. </script>
  156.  
  157. </body>
  158. </html>
下一节