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

on的绑定机制

我们通过 on 接口绑定一个事件的时候,内部做了一些比较复杂的处理:

主要针对几个设计问题的处理:

  1. 针对同一个对象的多个相同/不同的事件如何处理?
  2. 命名空间的处理?
  3. 自定义事件是如何触发的?
  4. 全局事件如何出发?

具体很多问题我们在后续课程中会一个一个解决,我们先看看如果同一个元素绑定多个相同或者不同的事件要如何处理?

首先我们脑海中先要有一个分离的概念,jQuery 的事件与数据其实是没有直接关联的关系,而且通过数据缓存去存储事件数据的。jQuery 从1.2.3版本引入数据缓存系统贯穿内部,为整个服务事件体系也引入了这个缓存机制,所以 jQuery 并没有将事件处理函数直接绑定到 DOM 元素上,而是通过 .data 存储在缓存 .cahce 上,所以事件的机制都是依赖之前的数据缓存模块的。

我们为了理解 jQuery.event.add 代码结构,适当的跳过这个环节中不能理解的代码。

第一步:获取数据缓存

//获取数据缓存
elemData = data_priv.get( elem );

通过缓存对象的 get 方法获取元素对应的存储数据,如果没有则内部会新建一个与 elem 元素映射的数据缓存区,用来存储之后用户将要要处理操作行为(事件与事件),这个处理主要是合并同个元素绑定多个事件的问题。

aaron.on('mousedown', 'li', function(e) {
  show('委托到li触发')
})
aaron.on('mousedown', 'ul', function(e) {
  show('委托到ul触发')
})
aaron.on('mousedown', 'div', function(e) {
  show('委托到div触发')
})
aaron.on('mousedown', function(e) {
  show('mousedown')
})

如上同一个元素上绑定了 4 次不同的行为,但是都是针对同一个元素的所以这个地方我们就需要把事件与数据都合并到同一个缓存区,这样每次重复操作都不会在去创建一个新的缓存了。

第二步:创建编号

if( !handler.guid ) {               
 handler.guid= jQuery.guid++;           
}

为每一个事件的句柄给一个标示,添加 ID 的目的是用来寻找或者删除 handler,因为这个东东是缓存在缓存对象上的,没有直接跟元素节点发生关联。

第三步:给缓存增加事件处理句柄

if ( !(events = elemData.events) ) {
    events = elemData.events= {};
}
if ( !(eventHandle = elemData.handle) ) {
    eventHandle = elemData.handle = function( e ) {
        return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ?
            jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
            undefined;
    };
}

events,eventHandle 都是 elemData 缓存对象内部的,可见在elemData中有两个重要的属性:

  1. 一个是 events,是jQuery内部维护的事件列队
  2. 一个是 handle,是实际绑定到elem中的事件处理函数

之后的代码无非就是对这 2 个对象的筛选、分组、填充了。

第四步: 填充事件名与事件句柄

事件名称可以添加指定的 event namespaces(命名空间) 来简化删除或触发事件。例如,"click.myPlugin.simple" 为 click 事件同时定义了两个命名空间 myPlugin 和 simple。通过上述方法绑定的 click 事件处理,可以用 .off("click.myPlugin").off("click.simple") 删除绑定到相应元素的 Click 事件处理程序,而不会干扰其他绑定在该元素上的“click(点击)” 事件。命名空间类似 CSS 类,因为它们是不分层次的;只需要有一个名字相匹配即可。以下划线开头的名字空间是供 jQuery 使用的。所以要针对每个事件都需要绑定。

如:

.on('mouseup mousedown','p',function(e){
    console.log(e)
  })

其实就是填充events与eventHandle

elemData = {
       events:{}
       eventHandle:function(){}
}
handlers = events[type] = [];
handlers.delegateCount = 0;

这段比较长了分解下,最终的目的就是为填充events,eventHandle。

其实整个 add 方法下来就干了那么几件事:

  1. 利用 data_priv 数据缓存,分离事件与数据
  2. 元素与缓存中建立 guid 的映射关系用于查找
  3. 通过 elemData.events 合并同一个元素上的多个事件
  4. 通过空格分隔的多事件
  5. 引用了钩子处理特殊事件
  6. 如果委托元素,给对应的数据打上一个记录标记
  7. 最后通过 addEventListener 绑定事件,等待执行

所以我们可以看到在绑定的时候处理,都是为了后期做了大量的铺垫。

任务

  1. <!doctype html>
  2. <html>
  3. <head>
  4. <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
  5. <style>
  6. p { background:yellow; font-weight:bold; cursor:pointer;
  7. padding:5px; }
  8. p.over { background: #ccc; }
  9. span { color:red; }
  10. </style>
  11. <script src="http://www.imooc.com/static/lib/jquery/1.9.1/jquery.js" type="text/javascript"></script>
  12. </head>
  13. <body>
  14.  
  15.  
  16. <script>
  17.  
  18. ////////////////////
  19. //事件on接口的内部直接绑定方法 //
  20. ////////////////////
  21. add: function(elem, types, handler, data, selector) {
  22.  
  23. var handleObjIn, eventHandle, tmp,
  24. events, t, handleObj,
  25. special, handlers, type, namespaces, origType,
  26. //事件缓存
  27. elemData = data_priv.get(elem);
  28.  
  29. // 检测状态,若为空数据、text或comment节点时,阻止绑定事件
  30. if (!elemData) {
  31. return;
  32. }
  33.  
  34. // 一般在第一运行的时候,handler为事件处理函数,后面jQuery对handler做了一些包装
  35. // 检测handler是包含handler和selector的对象,包含说明handler是一个事件处理函数包
  36. if (handler.handler) {
  37. handleObjIn = handler;
  38. handler = handleObjIn.handler;
  39. selector = handleObjIn.selector;
  40. }
  41.  
  42. //检测handler是否存在ID (guid),如果没有那么传给他一个ID
  43. //添加ID的目的是 用来寻找或者删除handler
  44. if (!handler.guid) {
  45. handler.guid = jQuery.guid++;
  46. }
  47.  
  48. //给缓存增加事件处理空间
  49. //elemData = {
  50. // events:
  51. // handle:
  52. //}
  53. //同一个元素,不同事件,不重复绑定
  54. if (!(events = elemData.events)) {
  55. events = elemData.events = {};
  56. }
  57. if (!(eventHandle = elemData.handle)) {
  58. eventHandle = elemData.handle = function(e) {
  59. // Discard the second event of a jQuery.event.trigger() and
  60. // when an event is called after a page has unloaded
  61. return typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ?
  62. jQuery.event.dispatch.apply(elem, arguments) : undefined;
  63. };
  64. }
  65.  
  66. // Handle multiple events separated by a space
  67. // 通过空格分隔的多事件
  68. types = (types || "").match(rnotwhite) || [""];
  69. t = types.length;
  70. while (t--) {
  71. // 如"mouseover.a.b" → ["mouseover.a.b", "mouseover", "a.b"]
  72. tmp = rtypenamespace.exec(types[t]) || [];
  73. type = origType = tmp[1];
  74. //取出事件命名空间,如a.b,并根据"."分隔成数组
  75. namespaces = (tmp[2] || "").split(".").sort();
  76.  
  77. // There *must* be a type, no attaching namespace-only handlers
  78. //检测状态,若为空数据、text或comment节点时,阻止绑定事件
  79. if (!type) {
  80. continue;
  81. }
  82.  
  83. // If event changes its type, use the special event handlers for the changed type
  84. // 事件是否会改变当前状态,如果会则使用特殊事件
  85. // click beforeunload blur
  86. special = jQuery.event.special[type] || {};
  87.  
  88. // If selector defined, determine special event api type, otherwise given type
  89. // 根据是否已定义selector,决定使用哪个特殊事件api,如果没有非特殊事件,则用type
  90. // focus -> delegateType:focusin
  91. // blur -> delegateType:focusout
  92. type = (selector ? special.delegateType : special.bindType) || type;
  93.  
  94. // Update special based on newly reset type
  95. special = jQuery.event.special[type] || {};
  96.  
  97. // handleObj is passed to all event handlers
  98. handleObj = jQuery.extend({
  99. type: type,
  100. origType: origType,
  101. data: data,
  102. handler: handler,
  103. guid: handler.guid,
  104. selector: selector,
  105. needsContext: selector && jQuery.expr.match.needsContext.test(selector),
  106. namespace: namespaces.join(".")
  107. }, handleObjIn);
  108.  
  109. // Init the event handler queue if we're the first
  110. if (!(handlers = events[type])) {
  111. handlers = events[type] = [];
  112. handlers.delegateCount = 0;
  113.  
  114. // Only use addEventListener if the special events handler returns false
  115. // 如果获取特殊事件监听方法失败,则使用addEventListener进行添加事件
  116. if (!special.setup || special.setup.call(elem, data, namespaces, eventHandle) === false) {
  117. if (elem.addEventListener) {
  118. elem.addEventListener(type, eventHandle, false);
  119. }
  120. }
  121. }
  122.  
  123. if (special.add) {
  124. special.add.call(elem, handleObj);
  125.  
  126. if (!handleObj.handler.guid) {
  127. handleObj.handler.guid = handler.guid;
  128. }
  129. }
  130.  
  131. // Add to the element's handler list, delegates in front
  132. //有委托元素 ,增加委托标记
  133. if (selector) {
  134. handlers.splice(handlers.delegateCount++, 0, handleObj);
  135. } else {
  136. handlers.push(handleObj);
  137. }
  138.  
  139. // Keep track of which events have ever been used, for event optimization
  140. jQuery.event.global[type] = true;
  141. }
  142.  
  143. }
  144.  
  145.  
  146. </script>
  147.  
  148. </body>
  149. </html>
下一节