Js代码  收藏代码
  1. 作者:nuysoft/高云 QQ:47214707 EMail:nuysoft@gmail.com
  2. 声明:本文为原创文章,如需转载,请注明来源并保留原文链接。
  3. 后文预告:封装事件对象 便捷接口解析 ready专题
Js代码  收藏代码
  1. 10.4    .bind() .one()
  2. 10.4.1  如何使用
  3. .bind( eventType, [eventData], handler(eventObject) )   在匹配的元素上绑定指定类型的事件处理函数
  4. .bind( eventType, [eventData], preventBubble )  第三个参数为false,则阻止浏览器默认行为并停止事件冒泡,默认true
  5. .bind( events ) 绑定多个事件,events的键为事件类型,值为对应的事件处理函数
  6. .one( eventType, [eventData], handler(eventObject) )    在匹配的元素上绑定指定类型的事件处理函数,这个函数最多执行一次
  7. 其实是改写了事件处理函数,函数执行时先解绑定,再执行
  8. 10.4.2  调用过程
  9. jQuery.fn.bind → jQuery.event.add
  10. jQuery.fn.one → jQuery.event.add
  11. 10.4.3  源码分析
  12. /**
  13. * bind:
  14. * .bind( eventType, [eventData], handler(eventObject) ) 在匹配的元素上绑定指定类型的事件处理函数
  15. * .bind( eventType, [eventData], preventBubble ) 第三个参数为false,则阻止浏览器默认行为并停止事件冒泡,默认true
  16. * .bind( events ) 绑定多个事件,events的键为事件类型,值为对应的事件处理函数
  17. *
  18. * one:
  19. * .one( eventType, [eventData], handler(eventObject) ) 在匹配的元素上绑定指定类型的事件处理函数,这个函数最多执行一次
  20. * 其实是改写了事件处理函数,函数执行时先解绑定,再执行
  21. */
  22. jQuery.each(["bind""one"], function( i, name ) {
  23. // 为jQuery对象扩展bind、one方法
  24. jQuery.fn[ name ] = function( type, data, fn ) {
  25. var handler;
  26. // Handle object literals
  27. // 绑定多个事件,key为事件类型,type[key]是事件处理函数
  28. if ( typeof type === "object" ) { //
  29. for ( var key in type ) {
  30. this[ name ](key, data, type[key], fn);
  31. }
  32. return this;
  33. }
  34. // 修正参数:如果没有传入data,或data为false,(jQuery的这种写法很巧妙,但是可读性太差)
  35. // 如果有两个参数,则认为忽略了data:type, fn,结果是:type, undefined, fn
  36. // 如果data是false,则认为是type, false, fn,结果是:type, undefined, false
  37. if ( arguments.length === 2 || data === false ) {
  38. fn = data;
  39. data = undefined;
  40. }
  41. if ( name === "one" ) { // 只执行一次
  42. handler = function( event ) { // 创建一个新的事件处理函数句柄
  43. jQuery( this ).unbind( event, handler ); // 先删除事件
  44. return fn.apply( this, arguments ); // 再执行传入的事件处理函数fn,这里this是DOM元素
  45. };
  46. handler.guid = fn.guid || jQuery.guid++; // 同步guid,重新包装后的函数与原始函数的guid统一
  47. else {
  48. handler = fn;
  49. }
  50. if ( type === "unload" && name !== "one" ) { // 如果是unload事件,则只执行一次
  51. this.one( type, data, fn ); // 迭代调用,没有清晰的结构很危险
  52. else {
  53. for ( var i = 0, l = this.length; i < l; i++ ) {
  54. jQuery.event.add( this[i], type, handler, data ); // 遍历匹配的元素,并绑定事件处理函数
  55. }
  56. }
  57. // 返回jQuery对象,使得可以继续链式调用
  58. return this;
  59. };
  60. });
  61. 10.4.4  jQuery.event.add
  62. .bind()和.one()都调用了jQuery.event.add来实现,为元素elem添加类型types的句柄handler,事实上所有的事件绑定最后都通过jQuery.event.add来实现。其执行过程大致如下:
  63. 1.  先调用jQuery._data从$.cache中取出已有的事件缓存(私有数据,Cache的解析详见数据缓存)
  64. 2.  如果是第一次在DOM元素上绑定该类型事件句柄,在DOM元素上绑定jQuery.event.handle,作为统一的事件响应入口
  65. 3.  将封装后的事件句柄放入缓存中
  66. 传入的事件句柄,会被封装到对象handleObj的handle属性上,此外handleObj还会填充guid、type、namespace、data属性;DOM事件句柄elemData.handle指向jQuery.event.handle,即jQuery在DOM元素上绑定事件时总是绑定同样的DOM事件句柄jQuery.event.handle。
  67. 事件句柄在缓存$.cache中的数据结构如下,事件类型和事件句柄都存储在属性events中,属性handle存放的执行这些事件句柄的DOM事件句柄:
  68. elemData = {
  69. events: {
  70. 'click' : [
  71. { guid: 5, type: 'click', namespace: '', data: undefined,
  72. handle: { guid: 5, prototype: {} }
  73. },
  74. { ... }
  75. ],
  76. 'keypress' : [ ... ]
  77. },
  78. handle: { // DOM事件句柄
  79. elem: elem,
  80. prototype: {}
  81. }
  82. }
  83. 看看jQuery.event.add的源码解析:
  84. jQuery.event = {
  85. // Bind an event to an element
  86. // Original by Dean Edwards
  87. // 为元素elem添加类型types的句柄handler
  88. add: function( elem, types, handler, data ) {
  89. // 忽略 Text Comment
  90. if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
  91. return;
  92. }
  93. if ( handler === false ) { // 如果事件处理函数是false,则用returnFalse代替false
  94. handler = returnFalse; // returnFalse会取消事件的默认行为
  95. else if ( !handler ) {
  96. // Fixes bug #7229. Fix recommended by jdalton
  97. return// 如果handler是undefined null '',则直接返回,不执行后边的代码
  98. }
  99. var handleObjIn, handleObj;
  100. if ( handler.handler ) { // 如果已经是封装过的jQuery事件对象(JS真是愁人啊,弱类型,只能通过特性、属性判断对象的类型)
  101. handleObjIn = handler; // handleObj是DOM事件句柄对象(丰富后的jQuery.event.handle)
  102. handler = handleObjIn.handler; // handler始终是个函数
  103. }
  104. // Make sure that the function being executed has a unique ID
  105. if ( !handler.guid ) { // 如果没有分配唯一id,则分配一个
  106. handler.guid = jQuery.guid++;
  107. }
  108. // Init the element's event structure
  109. // 内部数据存储在内部对象上,因此elemData不应该为空,除非elem不支持jQuery缓存(embed、object、applet)
  110. var elemData = jQuery._data( elem ); // 仅仅调用_data接口一次,后续直接在返回的对象上操作
  111. // If no elemData is found then we must be trying to bind to one of the
  112. // banned noData elements
  113. // 如果elemData不存在,说明是一个不支持缓存的元素上绑定事件,直接返回
  114. if ( !elemData ) {
  115. return;
  116. }
  117. var events = elemData.events, // 事件类型和事件句柄都存储在属性events中
  118. eventHandle = elemData.handle; // 属性handle存放的执行这些事件句柄的DOM事件句柄
  119. if ( !events ) {
  120. elemData.events = events = {}; // 初始化一个存放事件的对象,事件名为key,事件函数为value
  121. }
  122. if ( !eventHandle ) { // 初始化一个执行事件函数的函数
  123. elemData.handle = eventHandle = function( e ) {
  124. // Discard the second event of a jQuery.event.trigger() and
  125. // when an event is called after a page has unloaded
  126. // 调用jQuery.event.handler
  127. // jQuery.event.handler是什么意思?
  128. return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
  129. jQuery.event.handle.apply( eventHandle.elem, arguments ) :
  130. undefined;
  131. };
  132. }
  133. // Add elem as a property of the handle function
  134. // This is to prevent a memory leak with non-native events in IE.
  135. // 内存泄漏?
  136. // 将elem作为eventHandle的属性存储,用来避免IE中非本地事件的内存泄漏
  137. eventHandle.elem = elem;
  138. // Handle multiple events separated by a space
  139. // jQuery(...).bind("mouseover mouseout", fn);
  140. types = types.split(" "); // 同时绑定的多个事件可以用空格隔开
  141. // 为什么有的地方用字符串直接量,有的地方要用正则呢?因为正则可以进行全局匹配g
  142. var type, i = 0, namespaces;
  143. while ( (type = types[ i++ ]) ) { // 又一种遍历数组的方法,故意秀技巧么?
  144. handleObj = handleObjIn ?
  145. jQuery.extend({}, handleObjIn) :
  146. { handler: handler, data: data }; // 创建事件句柄对象,后边还会添加属性:guid anmespace type
  147. // Namespaced event handlers
  148. // 如果事件字符串中有句号.,则说明有命名空间
  149. if ( type.indexOf(".") > -1 ) {
  150. namespaces = type.split(".");
  151. type = namespaces.shift(); // 取出第一个元素
  152. handleObj.namespace = namespaces.slice(0).sort().join("."); // 将于下部分作为命名空间,存储到属性namespace中
  153. else {
  154. namespaces = [];
  155. handleObj.namespace = ""// 没有命名空间
  156. }
  157. handleObj.type = type; // 事件类型
  158. if ( !handleObj.guid ) {
  159. handleObj.guid = handler.guid; // 事件对象唯一id
  160. }
  161. // Get the current list of functions bound to this event
  162. var handlers = events[ type ], // 事件句柄队列,取出已经存在函数数组
  163. special = jQuery.event.special[ type ] || {}; // 特殊处理的事件
  164. // Init the event handler queue
  165. if ( !handlers ) { // 如果没有绑定事件,则初始化为一个空数组
  166. handlers = events[ type ] = []; //
  167. // Check for a special event handler
  168. // Only use addEventListener/attachEvent if the special
  169. // events handler returns false
  170. // 如果特殊事件没有setup属性 或 setup返回false,则用浏览器原生的绑定事件接口addEventListener/addEventListener,例如live事件
  171. if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
  172. // Bind the global event handler to the element
  173. // 绑定事件句柄
  174. if ( elem.addEventListener ) {
  175. elem.addEventListener( type, eventHandle, false ); // jQuery绑定的事件默认都是起泡阶段捕获
  176. else if ( elem.attachEvent ) {
  177. elem.attachEvent( "on" + type, eventHandle ); // IE事件模型中没有2级DOM事件模型具有的事件捕捉的概念,只有起泡阶段
  178. }
  179. }
  180. }
  181. if ( special.add ) { // 如果有add方法,就调用
  182. special.add.call( elem, handleObj );
  183. if ( !handleObj.handler.guid ) { // 始终保证事件句柄有唯一的id
  184. handleObj.handler.guid = handler.guid;
  185. }
  186. }
  187. // Add the function to the element's handler list
  188. // 将句柄对象handleObj,加入到句柄数组handler中,
  189. // handleObj包含以下属性:data 唯一guid 命名空间namespace 事件类型type handler函数
  190. handlers.push( handleObj );
  191. // Keep track of which events have been used, for event optimization
  192. // 记录已经使用的事件,用于事件优化?怎么个优化?
  193. jQuery.event.global[ type ] = true;
  194. }
  195. // Nullify elem to prevent memory leaks in IE
  196. // 将elem置为null,避免IE中的内存泄漏(这行代码真是坑爹啊,没有无数次的测试确认,怎么可能想到这行代码呢)
  197. elem = null;
  198. },
  199. // ...
  200. };
  201. 10.5    .unbind()
  202. 10.5.1  如何使用
  203. .unbind( [eventType] [, handler(eventObject)] ) 从匹配的元素上删除一个之前绑定的事件句柄
  204. 如果没有参数,删除所有事件句柄
  205. .unbind( eventType, false ) 删除通过.bind( eventType, false )绑定的事件句柄
  206. .unbind( event )    看不懂。。。
  207. 10.5.2  调用过程
  208. jQuery.fn.unbind → jQuery.event.remove
  209. 10.5.3  源码分析
  210. jQuery.fn.extend({
  211. // 删除句柄:删除一个或多个之前附加的事件句柄,jQuery事件只在起泡阶段捕获,不需要再定义控制捕获阶段的参数
  212. unbind: function( type, fn ) {
  213. // Handle object literals
  214. // 一次删除多个事件句柄,key是事件类型,type[key]是事件句柄(这里是迭代复用)
  215. // !type.preventDefault 表明这不是一个就jQuery事件对象
  216. if ( typeof type === "object" && !type.preventDefault ) {
  217. for ( var key in type ) {
  218. this.unbind(key, type[key]);
  219. }
  220. // 到这里,type可能是字符串,也可能是jQuery事件对象
  221. else {
  222. for ( var i = 0, l = this.length; i < l; i++ ) {
  223. jQuery.event.remove( this[i], type, fn ); // 调用remove删除句柄
  224. }
  225. }
  226. return this;
  227. },
  228. // ...
  229. };
  230. 10.5.4  jQuery.event.remove
  231. .unbind()通过调用jQuery.event.remove,删除之前绑定的一个或多个事件句柄,事实上所有的句柄删除最后都通过jQuery.event.remove实现,其执行过程大致如下:
  232. 1. 现调用jQuery._data从缓存$.cache中取出elem对应的所有数组(内部数据,与调用jQuery.data存储的数据稍有不同
  233. 2. 如果未传入types则移除所有事件句柄,如果types是命名空间,则移除所有与命名空间匹配的事件句柄
  234. 3. 如果是多个事件,则分割后遍历
  235. 4. 如果未指定删除哪个事件句柄,则删除事件类型对应的全部句柄,或者与命名空间匹配的全部句柄
  236. 5. 如果指定了删除某个事件句柄,则删除指定的事件句柄
  237. 6. 所有的事件句柄删除,都直接在事件句柄数组jQuery._data( elem ).events[ type ]上调用splice操作
  238. 7. 最后检查事件句柄数组的长度,如果为0,或为1但要删除,则移除绑定在elem上DOM事件
  239. 8. 最后的最后,如果elem对应的所有事件句柄events都已删除,则从缓存中移走elem的内部数据
  240. 9. 在以上的各个过程,都要检查是否有特例需要处理
  241. 看看它的源码分析:
  242. jQuery.event = {
  243. // ...
  244. /**
  245. * 移除元素elem上的一个或多个或一组事件
  246. * pos 指要移除的是第几个types事件,可以减少遍历次数
  247. * handler的属性guid唯一标识某个事件对象,移除时通过比较guid是否相等
  248. */
  249. remove: function( elem, types, handler, pos ) { // add/remove都是作为jQuery内部接口使用
  250. // don't do events on text and comment nodes
  251. // 忽略文本元素Text和注释元素Comment
  252. if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
  253. return;
  254. }
  255. if ( handler === false ) { // 替换布尔型handler为函数,保证handler始终是一个函数,使得remove接口的使用更加便捷
  256. handler = returnFalse;
  257. }
  258. var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType,
  259. elemData = jQuery.hasData( elem ) && jQuery._data( elem ), // 如果在缓存cache中有数据,则取出,否则elemData为undefine
  260. // 因为jQuery._data总是返回一个对象,因此要先判断缓存cache中是否有数组
  261. events = elemData && elemData.events; // 取出事件句柄数组,用正则表达式可以将多行代码合并为一行,这里可以用if-else或三元表达式代替
  262. if ( !elemData || !events ) { // 如果未绑定,则忽略本次调用,直接返回
  263. return;
  264. }
  265. // types is actually an event object here
  266. // 如果types是一个jQuery Event对象,则。。。
  267. if ( types && types.type ) {
  268. handler = types.handler;
  269. types = types.type;
  270. }
  271. // Unbind all events for the element
  272. // 如果types为false(强制转换类型),则移除所有事件
  273. // 如果types是字符串,并且以句号开头,则移除types命名空间下的指定事件(type+types)
  274. if ( !types || typeof types === "string" && types.charAt(0) === "." ) {
  275. types = types || "";
  276. for ( type in events ) { // 遍历events,移除所有已绑定的事件,这里是迭代调用,jQuery的源码很精致,能复用的代码会尽量复用
  277. jQuery.event.remove( elem, type + types ); // 全部事件,或某一命名空间下的全部事件
  278. }
  279. return;
  280. }
  281. // Handle multiple events separated by a space
  282. // jQuery(...).unbind("mouseover mouseout", fn);
  283. // 同时移除多个事件,事件之间用空格隔开,使得remove接口很灵活
  284. types = types.split(" ");
  285. // 个人觉的变量i的定义与使用离得那么远,不是好习惯,像这种局部使用的变量,应该哪里用就在哪里定义
  286. // 可能作者觉得集中定义显得代码不凌乱吧
  287. while ( (type = types[ i++ ]) ) { // 有一种循环方式
  288. origType = type;
  289. handleObj = null;
  290. all = type.indexOf(".") < 0; // all在这里表示移除的是type命名空间下的全部事件对象,小于0表示不是以.开头
  291. // all为true表示是事件,false表示命名空间
  292. namespaces = [];
  293. if ( !all ) { // 如果以.开头,即type是命名空间,构建命名空间正则表达式
  294. // Namespaced event handlers
  295. namespaces = type.split("."); // 全部命名空间
  296. type = namespaces.shift(); // 取出第一个作为事件类型,其余的为命名空间
  297. namespace = new RegExp("(^|\\.)" +
  298. jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)");
  299. // 这里要动态创建正则,因此用了 new RegExp 的方式
  300. }
  301. eventType = events[ type ]; // 取出type对应的事件对象数组
  302. if ( !eventType ) { // 如果type对应的事件对象数组不存在,则跳过本次循环(既然不存在就不必移除了)
  303. continue;
  304. }
  305. if ( !handler ) { // 没有指定移除哪个事件对象,handler其实是一个带有guid属性的函数,因为通过guid可以找到对应的事件对象,因此这里依然称handler为事件对象
  306. for ( j = 0; j < eventType.length; j++ ) { // 遍历事件对象数组,这里因为是遍历一个动态的数组,所以没有定义eventType.length变量
  307. handleObj = eventType[ j ];
  308. if ( all || namespace.test( handleObj.namespace ) ) { // 如果是事件或命名空间匹配
  309. jQuery.event.remove( elem, origType, handleObj.handler, j ); // 取出事件对象,迭代调用remove接口
  310. // handleObj.handler有guid属性,因此迭代调用remove依然可以匹配到对应handleObj,事实上这里也可以改写为传入handleObj
  311. // 注意最后一个参数j,remove的pos不为null,就不会在其他地方被splice删除,所以下一行才会splice(这个问题的处理太乱了)
  312. eventType.splice( j--, 1 ); // 如果循环遍历的是一个变化的数组,则可以用这种方式:j++之前先执行j--,保证不会因为数组下标的错误导致某些数组元素遍历不到,秀!
  313. }
  314. }
  315. // 如果未指定删除哪个事件句柄,则删除事件类型对应的全部句柄,或者与命名空间匹配的全部句柄
  316. // 这里对$.cache的操作,是通过对 eventType = jQuery._data( elem ).events[ type ]的直接操作进行维护,直接操作事件句柄数组
  317. // 如果是我,可能会再开发一个接口供客户端进行这种微操,其实没必要。
  318. continue;
  319. }
  320. // 到这里,说明指定事件句柄handler
  321. special = jQuery.event.special[ type ] || {}; // 特例
  322. // 如果没有指定pos,则默认从0开始遍历,这里的pos可以减少遍历次数,提高性能,多么精致的代码
  323. // 本人喜欢这种极致的代码,更喜欢优雅的代码,更注重代码的可读性,稍微的性能下降可以接受
  324. for ( j = pos || 0; j < eventType.length; j++ ) {
  325. handleObj = eventType[ j ];
  326. if ( handler.guid === handleObj.guid ) { // 比较事件函数的guid和时间对象的guid,===可以避免类型转换,稍微提高性能
  327. // remove the given handler for the given type
  328. if ( all || namespace.test( handleObj.namespace ) ) {
  329. if ( pos == null ) { // 如果没有指定pos,匹配到后就删除
  330. eventType.splice( j--, 1 ); // j++之前先j--,避免数组变化导致遍历错误
  331. }
  332. if ( special.remove ) { // 如果有自定义的remove方法,则调用(似乎只有live有add remove方法)
  333. special.remove.call( elem, handleObj );
  334. }
  335. }
  336. // 因为guid是全局唯一的,所以匹配到guid对应的事件就可以退出了
  337. if ( pos != null ) { // 如果pos不为null,说明是要删除指定的事件对象,任务完成,退出
  338. break;
  339. }
  340. }
  341. }
  342. // remove generic event handler if no more handlers exist
  343. // 如果type对应的事件对象数组为空,或者发现只剩一个,则移除绑定在elem上浏览器原生事件
  344. if ( eventType.length === 0 || pos != null && eventType.length === 1 ) {
  345. // 检查special是否有自定义的teardown,优先调用
  346. // 这一行的巧妙支出在于:
  347. // 1. special不会为null或undefined,如果没有找到会被赋值为{},是空设置模式的应用
  348. // 2. 首先巧妙的判断special.teardown是否存在,如果不存在则认为是普通事件,如果存在则可以调用teardown
  349. // 3. 在jQuery源码中你看到过没有返回值的函数码?似乎没有,teardown返回false表示失败,还得用普通方式删除
  350. if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
  351. jQuery.removeEvent( elem, type, elemData.handle );
  352. }
  353. ret = null// 这个变量搞笑了,自从定义之后就从来就没用过,明显多余,可以遗留代码里的变量吧
  354. delete events[ type ]; // 删除类型属性,还是直接对数组句柄数组操作,根本没有什么缓存微操接口
  355. }
  356. }
  357. // Remove the expando if it's no longer used
  358. // 从jQuery.cache中移除elem的数据,最后检查elemData.events
  359. if ( jQuery.isEmptyObject( events ) ) {
  360. var handle = elemData.handle;
  361. if ( handle ) {
  362. handle.elem = null// 置空,删除对HTML元素的引用,避免因垃圾回收机制不起作用导致的浏览器内存泄漏
  363. // 这是一个非常好的技巧,我们在JavaScript中引用完HTML元素后一定要置空。
  364. }
  365. delete elemData.events; // 删除存储事件句柄的对象
  366. delete elemData.handle; // 删除DOM事件句柄
  367. if ( jQuery.isEmptyObject( elemData ) ) { // 都删了难道还不是isEmptyObject?有点多余,除非某些浏览器不支持delete,可问题是不支持也得移除数据,还是多余!
  368. jQuery.removeData( elem, undefined, true ); // 彻底的从缓存中移走elem的内部数据(存储在属性expando上)
  369. }
  370. }
  371. },
  372. // ...
  373. };
  374. 10.6    .live() .die()
  375. 10.6.1  如何使用
  376. live 在匹配当前选择器的元素上绑定一个事件处理函数,包括已经存在的和未来添加的,即任何添加的元素只要匹配当前选择器,就会被绑定事件处理函数
  377. .live( eventType, handler ) 在匹配当前选择器的元素上绑定一个指定类型的事件处理函数
  378. .live( eventType, eventData, handler )  eventData可以传递数据给事件处理函数
  379. .live( events ) 绑定多个事件
  380. .die()  删除之前通过.live()绑定的全部事件句柄
  381. .die( eventType [, handler] )   eventType是字符串事件类型,删除一个或一类之前通过.live()绑定的事件句柄
  382. .die( eventTypes )  eventTypes是Map对象,删除多类事件句柄
  383. 10.6.2  调用过程
  384. .live()方法永远不会将事件绑定到匹配的元素上,而是将事件绑定到祖先元素(document或context),由该祖先元素代理子元素的事件。
  385. 以 $('.clickme').live('click'function() { … } ) 为例,当在新添加的元素上点击时,执行过程如下:
  386. 1. click事件被生成,并传递给<div>,待<div>处理
  387. 2. 因为<div>没有直接绑定click事件,因此事件沿着DOM树进行冒泡传递
  388. 3. 事件冒泡传递直到到达DOM树的根节点,在根节点上绑定了.live()指定的原始事件处理函数(在1.4以后的版本中,.live()绑定事件到上下文context,以提升性能)
  389. 4. 执行.live()指定的事件处理函数
  390. 5. 原始事件处理函数检查event的target属性以确定是否继续执行,检查的方式是 $(event.target).closes
  391. 6. 如果匹配,则原始事件处理函数执行,上下位被设置为找到的元素
  392. 因为第5步的检查是在执行原始事件处理函数之前,因此元素可以在任何时候添加,并且事件可以生效
  393. 调用过程:
  394. $('div').live( 'click'function(){ alert(1); } )
  395. jQuery.fn.live → jQuery.event.add → jQuery._data → jQuery.event.special.live.add → jQuery.event.add
  396. $('div').die( 'click'function(){ alert(1); } )
  397. jQuery.fn.die → jQuery.fn.unbind → jQuery.event.remove jQuery._data
  398. 调试过程中发现个有趣的现象:在父节点上绑定live事件,而不是具体的时间类型?
  399. 10.6.3  源码分析
  400. 看看.live()/.die()的源码分析:
  401. // 事件类型修正
  402. var liveMap = {
  403. focus: "focusin",
  404. blur: "focusout",
  405. mouseenter: "mouseover",
  406. mouseleave: "mouseout"
  407. };
  408. // die 移除用live附加的一个或全部事件处理函数
  409. // 对应关系:bind-unbind, live-die, delegate-undelegate
  410. /**
  411. * live 在匹配当前选择器的元素上绑定一个事件处理函数,包括已经存在的,和未来添加的,即任何添加的元素只要匹配当前选择器就会被绑定事件处理函数
  412. * .live( eventType, handler ) 在匹配当前选择器的元素上绑定一个指定类型的事件处理函数
  413. * .live( eventType, eventData, handler ) eventData可以传递给事件处理函数
  414. * .live( events ) 绑定多个事件
  415. *
  416. *
  417. * 参考文档:
  418. * http://api.jquery.com/live
  419. * http://www.codesky.net/article/doc/201004/20100417042278.htm
  420. * http://hi.baidu.com/silveringsea/blog/item/55cd25016ecde30c1c958341.html
  421. *
  422. * 如何使用
  423. * 示例
  424. * 局限
  425. * 修正
  426. *
  427. * live被翻译为鲜活绑定、延迟绑定(更准确些)
  428. * live通过委派,而不是在匹配元素上直接绑定事件来实现
  429. *
  430. * 官方文档翻译:事件代理
  431. * .live()方法能对尚未添加到DOM文档中的元素生效。
  432. * .live()方法永远不会将事件绑定到匹配的元素上,而是将事件绑定到祖先元素(document或context),由该祖先元素代理子元素的事件。  *
  433. * 以 $('.clickme').live('click', function() { … } ) 为例,当在新添加的元素上点击时,执行过程如下:
  434. * 1. click事件被生成,并传递给<div>,待<div>处理
  435. * 2. 因为<div>没有直接绑定click事件,因此事件沿着DOM树进行冒泡传递
  436. * 3. 事件冒泡传递直到到达DOM树的根节点,在根节点上绑定了.live()指定的原始事件处理函数(在1.4以后的版本中,.live()绑定事件到上下文context,以提升性能)
  437. * 4. 执行.live()指定的事件处理函数
  438. * 5. 原始事件处理函数检查event的target属性以确定是否继续执行,检查的方式是 $(event.target).closest(".clickme")
  439. * 6. 如果匹配,则原始事件处理函数执行,上下位被设置为找到的元素
  440. *
  441. * 因为第5步的检查是在执行原始事件处理函数之前,因此元素可以在任何时候添加,并且事件可以生效
  442. *
  443. */
  444. jQuery.each(["live""die"], function( i, name ) {
  445. jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) {
  446. var type, i = 0, match, namespaces, preType,
  447. selector = origSelector || this.selector, // 选择器表达式
  448. // 很巧妙,如果没有origSelector,那么采用当前jQuery对象的选择器,、
  449. // 可是,如果origSelector为true,后边的处理对象就变成了以origSelector为选择器的jQuery对象
  450. context = origSelector ? this : jQuery( this.context ); // 上下文,
  451. // 这里也是,如果没有origSelector,那么上下文就变成当前jQuery对象的上下文
  452. // 可是,如果origSelector为true(类型转换),当前jQuery对象就变成上下文了
  453. // 就是在这里,通过一个内部变量,改变了选择器表达式和上下分,区分开了live/die和delegate/undelegate,用同样的代码实现了两种接口
  454. // 一次绑定或删除多个事件
  455. if ( typeof types === "object" && !types.preventDefault ) { // types不是jQuery事件对象
  456. for ( var key in types ) { // 迭代复用
  457. context[ name ]( key, data, types[key], selector );
  458. }
  459. return this;
  460. }
  461. // 如果是die,移除origSelector匹配元素上的所有通过live绑定的事件
  462. // 没有指定事件类型 + origSelector + origSelector是CSS选择器
  463. if ( name === "die" && !types &&
  464. origSelector && origSelector.charAt(0) === "." ) {
  465. context.unbind( origSelector ); // 事实上bind/unbind是基础,add/remove/tigger/handle是所有事件的基础
  466. return this;
  467. }
  468. // 修正参数(如果data为false或没有传入data参数)
  469. // 完整:types, data, fn, origSelector
  470. // types, false, fn, origSelector
  471. // 或 types, fn, origSelector
  472. if ( data === false || jQuery.isFunction( data ) ) {
  473. fn = data || returnFalse;
  474. data = undefined;
  475. }
  476. types = (types || "").split(" "); // 多个事件用空格隔开
  477. while ( (type = types[ i++ ]) != null ) { // 遍历事件类型数组
  478. match = rnamespaces.exec( type ); // 取到第一个.后的命名空间
  479. namespaces = "";
  480. if ( match )  { // 如果有命名空间,即事件类型后还有.
  481. namespaces = match[0]; // .+命名空间
  482. type = type.replace( rnamespaces, "" ); // 过滤.+命名空间,初学者会尝试用indexOf查找.的位置,然后截取
  483. }
  484. if ( type === "hover" ) { // 将hover分解为mouseenter mouseleave,继续遍历
  485. types.push( "mouseenter" + namespaces, "mouseleave" + namespaces );
  486. continue;
  487. }
  488. preType = type; //
  489. if ( liveMap[ type ] ) { // 修正事件名名,修正其实不准确,比如遇到focus,绑定了两个:focus focusin,不会造成两次事件响应么?
  490. types.push( liveMap[ type ] + namespaces ); // 将修正后的事件类型入队,因为用了while循环,所有不必担心遍历动态数组的问题
  491. type = type + namespaces; // 再恢复type,包含了命名空间,这不是蛋疼么,把命名空间去掉只为了检测hover和是否需要修正?
  492. else {
  493. type = (liveMap[ type ] || type) + namespaces; // 修正事件名,这里可以直接写成:type + namespaces
  494. // 这行的逻辑有点问题,已经知道liveMap[ type ]是false了,这个地方的代码有些重复
  495. }
  496. /**
  497. * 上边的if-else改写为:
  498. * if ( liveMap[ type ] ) types.push( liveMap[ type ] + namespaces )
  499. * type = type + namespaces
  500. */
  501. // 这里开始live的特殊处理,虽说live/die的逻辑比较接近,再加上delegate/undelegate,复用的粒度小但是很精髓
  502. // 同时为了减少接口,并没有将公共部分提取出来,总的来说,live/die这段代码出来的还是刚刚好
  503. if ( name === "live" ) {
  504. // bind live handler
  505. for ( var j = 0, l = context.length; j < l; j++ ) {
  506. // 绑定到上下文,事件类型经过liveConvert后变为 live.type.selector
  507. // 前边做了那么多铺垫,这行才是关键!
  508. // add: function( elem, types, handler, data ) {
  509. // context[j] 如果有origSelector则是当前jQuery对象,如果没有则是当前jQuery对象的上下文
  510. // live事件的格式比较特殊,应该trigger里会有live的特殊处理,live的处理在特例special里
  511. jQuery.event.add( context[j], "live." + liveConvert( type, selector ),
  512. { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } );
  513. }
  514. else {
  515. // unbind live handler
  516. // 删除live绑定的事件句柄
  517. context.unbind( "live." + liveConvert( type, selector ), fn );
  518. }
  519. }
  520. return this;
  521. };
  522. });
  523. /**
  524. * live执行句柄,live绑定的事件调用链:add.eventHandle → handler function liveHandler → _data → handler
  525. * @param event
  526. * @return
  527. */
  528. function liveHandler( event ) {
  529. var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret,
  530. elems = [],
  531. selectors = [],
  532. events = jQuery._data( this"events" );
  533. // Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911)
  534. if ( event.liveFired === this || !events || !events.live || event.target.disabled || event.button && event.type === "click" ) {
  535. return;
  536. }
  537. if ( event.namespace ) {
  538. namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)");
  539. }
  540. event.liveFired = this;
  541. var live = events.live.slice(0); // 返回一个新的数组,而不改变原来的数组
  542. for ( j = 0; j < live.length; j++ ) {
  543. handleObj = live[j];
  544. if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) {
  545. selectors.push( handleObj.selector );
  546. else {
  547. live.splice( j--, 1 );
  548. }
  549. }
  550. match = jQuery( event.target ).closest( selectors, event.currentTarget );
  551. for ( i = 0, l = match.length; i < l; i++ ) {
  552. close = match[i];
  553. for ( j = 0; j < live.length; j++ ) {
  554. handleObj = live[j];
  555. if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) && !close.elem.disabled ) {
  556. elem = close.elem;
  557. related = null;
  558. // Those two events require additional checking
  559. if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) {
  560. event.type = handleObj.preType;
  561. related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0];
  562. // Make sure not to accidentally match a child element with the same selector
  563. if ( related && jQuery.contains( elem, related ) ) {
  564. related = elem;
  565. }
  566. }
  567. if ( !related || related !== elem ) {
  568. elems.push({ elem: elem, handleObj: handleObj, level: close.level });
  569. }
  570. }
  571. }
  572. }
  573. for ( i = 0, l = elems.length; i < l; i++ ) {
  574. match = elems[i];
  575. if ( maxLevel && match.level > maxLevel ) {
  576. break;
  577. }
  578. event.currentTarget = match.elem;
  579. event.data = match.handleObj.data;
  580. event.handleObj = match.handleObj;
  581. ret = match.handleObj.origHandler.apply( match.elem, arguments );
  582. if ( ret === false || event.isPropagationStopped() ) {
  583. maxLevel = match.level;
  584. if ( ret === false ) {
  585. stop = false;
  586. }
  587. if ( event.isImmediatePropagationStopped() ) {
  588. break;
  589. }
  590. }
  591. }
  592. return stop;
  593. }
  594. // 在live事件类型type后增加上selector,作为命名空间
  595. function liveConvert( type, selector ) {
  596. // . > `
  597. // 空格 > &
  598. // 在type后增加上selector,作为命名空间
  599. return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspaces, "&"); //
  600. }
  601. 10.7    .deletege() .undelegate()
  602. 10.7.1  如何使用
  603. .delegate( selector, eventType, handler )   在匹配选择器表达式selector的元素上,将一个事件句handler柄绑定到一个或多个事件上
  604. 无论是现有的还是将来添加的,基于一组特定的根元素(即上下文)
  605. .delegate( selector, eventType, eventData, handler )    Map型对象eventData的属性将被复制到事件句柄handler
  606. .delegate( selector, events )   绑定一个或多个事件句柄
  607. .undelegate()   在当前jQuery对象上,移除所有live事件句柄
  608. .undelegate( selector, eventType, handler ) 在当前jQuery对象上,移除live. eventType.selector事件句柄
  609. 为当前选择所匹配的所有元素移除一个事件处理程序,现在或将来,基于一组特定的根元素。
  610. .undelegate( selector, events ) 在当前jQuery对象上,移除多个live事件句柄,events是Map型对象,key
  611. .undelegate( namespace )    在当前jQuery对象上,移除命名空间匹配的live事件句柄
  612. 从接口的含义看,.deletege()/.undelegate()与.live()/.die()的区别在于绑定事件时的上下文不同,上下文即事件绑定到的元素:
  613. .deletege()/.undelegate()   当前jQuery对象
  614. .live()/.die()  当前jQuery对象的上下文,经常是document,也可以是构造jQuery对象时指定的上下文
  615. 10.7.2  调用过程
  616. jQuery.fn.delegate → jQuery.fn.live → jQuery.event.add
  617. jQuery.fn.undelegate → jQuery.fn.unbind/die
  618. 10.7.3  源码分析
  619. // 事件代理,调用live方法实现
  620. delegate: function( selector, types, data, fn ) {
  621. // 因为传递了参数selector,live时的上下文变为this,而不是this.context
  622. return this.live( types, data, fn, selector );
  623. },
  624. // 删除事件代理,调用unbind或die实现
  625. undelegate: function( selector, types, fn ) {
  626. if ( arguments.length === 0 ) {
  627. return this.unbind( "live" ); // 在当前jQuery对象上直接unbind,不需要在去查找上下文
  628. else {
  629. // jQuery.fn[ die ] = function( types, data, fn, origSelector /* Internal Use Only */ ) {
  630. return this.die( types, null, fn, selector ); // 带有选择器字符串,改变了die里的上下文context变量
  631. }
  632. },
  633. 本节小节:
  634. 1.  .bind()/.unbind()高级接口的基础,add/remove/tigger/handle是所有的基础,最后才是调用浏览器原生接口
  635. 2.  .deletege()/.undelegate()与.live()/.die()的区别在于绑定事件时的父节点不同
  636. 后文预告:封装事件对象 便捷接口解析 ready专题

jQuery源码分析-10事件处理-Event-事件绑定与删除-bind/unbind+live/die+delegat/undelegate相关推荐

  1. jQuery源码分析-10事件处理-Event-事件绑定与删除-bind/unbind+live/die+delegat/unde

    10.4    .bind() .one() 10.4.1  如何使用 .bind( eventType, [eventData], handler(eventObject) )   在匹配的元素上绑 ...

  2. jQuery源码分析系列

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...

  3. [转]jQuery源码分析系列

    文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaro ...

  4. [转] jQuery源码分析-如何做jQuery源码分析

    jQuery源码分析系列(持续更新) jQuery的源码有些晦涩难懂,本文分享一些我看源码的方法,每一个模块我基本按照这样的顺序去学习. 当我读到难度的书或者源码时,会和<如何阅读一本书> ...

  5. jQuery源码分析-each函数

    本文部分截取自且行且思 jQuery.each方法用于遍历一个数组或对象,并对当前遍历的元素进行处理,在jQuery使用的频率非常大,下面就这个函数做了详细讲解: 复制代码代码 /*! * jQuer ...

  6. Kafka源码分析10:副本状态机ReplicaStateMachine详解 (图解+秒懂+史上最全)

    文章很长,建议收藏起来,慢慢读! Java 高并发 发烧友社群:疯狂创客圈 奉上以下珍贵的学习资源: 免费赠送 经典图书:<Java高并发核心编程(卷1)> 面试必备 + 大厂必备 +涨薪 ...

  7. jQuery源码分析之$.ajax方法

    请阅读我其它的关于inspectPrefiltersOrTransport以及ajaxTransport等相关博文,请了解readyState状态码 针对获取到location.href的兼容代码: ...

  8. jquery 源码分析初步

    jquery 所有版本下载和引用地址 http://www.jq22.com/jquery-info122 一 jquery源码要点 jQuery框架的核心就是从HTML文档中匹配元素并对其执行操作 ...

  9. AOSP源码分析:Android Input事件的产生、读取和分发

    大家好,今天为大家推荐来自MIUI的Cheeeelok同学的AOSP源码分析系列文章,本文依然从源码的角度带大家理解Android Input事件的产生.读取和分发.还没有看过作者上一篇文章 Andr ...

最新文章

  1. 【Linux】【通信】1.ping不通
  2. linux命令查看几位,Linux每周几个命令(一)--查找篇
  3. 森林正版服务器,The Forest 专用服务器设置向导
  4. 音视频技术开发周刊 | 225
  5. mysql sql 检测磁盘_MySQL 数据库磁盘占用情况查询
  6. 3层vni vxlan_方便业务迁移,大型企业数据中心VXLAN大二层基础,一分钟了解下
  7. 【java】高并发之限流 RateLimiter使用
  8. 457. 括号匹配二
  9. C++之析构函数探究
  10. python是什么东西
  11. linux安装gd,linux下 安装GD
  12. 手把手教你玩转YOLOX--Windows(上)
  13. SAP SM36如何设置后台作业 每月最后一天运行
  14. 谷歌浏览器下载速度很慢,怎么解决?
  15. Google VR开发-Cardboard VR SDK头部追踪实现(罗德里格旋转公式)
  16. 教大家电脑重装系统后硬盘消失要如何解决
  17. 住房公积金提取不再难,个人直接通过客户端搞定!
  18. Android 自定义dialog 设置宽度的问题
  19. 【学习笔记】ARC149
  20. 数码数字字体_数码相机的分类及单反数码相机、卡片数码相机的介绍

热门文章

  1. JAVA制作弹出小广告的程序_微信小程序实现首页弹出广告
  2. F015-“信息不对称”是伪科学 #F750
  3. 大数据-Hadoop(环境搭建)
  4. 40%带宽成本节约!京东云视频云正式支持AV1编码
  5. JavaSE进阶回顾第三天-异常
  6. bugku web18 秋名山车神
  7. 常用短距离无线通信优缺点的纵横比较
  8. 基于ssm的校园二手物品交易平台(idea+spring+springmvc+mybatis+jsp)
  9. Unity热更新系列之一: bundle打包和打包策略
  10. 众多场景已经全面普及智能取餐柜