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


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. 
27.        // Handle object literals
28.        // 绑定多个事件,key为事件类型,type[key]是事件处理函数
29.        if (typeof type ==="object" ) {//
30.            for (var keyin type ) {
31.                this[ name ](key, data, type[key], fn);
32.            } 
33.            returnthis;
34.        } 
35.        // 修正参数:如果没有传入data,或data为false,(jQuery的这种写法很巧妙,但是可读性太差)
36.        // 如果有两个参数,则认为忽略了data:type, fn,结果是:type, undefined, fn
37.        // 如果data是false,则认为是type, false, fn,结果是:type, undefined, false
38.        if ( arguments.length === 2 || data ===false ) {
39.            fn = data; 
40.            data = undefined; 
41.        } 
42. 
43.        if ( name ==="one" ) {// 只执行一次
44.            handler = function( event ) { // 创建一个新的事件处理函数句柄
45.                jQuery( this ).unbind( event, handler ); // 先删除事件
46.                return fn.apply(this, arguments );// 再执行传入的事件处理函数fn,这里this是DOM元素
47.            }; 
48.            handler.guid = fn.guid || jQuery.guid++;// 同步guid,重新包装后的函数与原始函数的guid统一
49.        } else {
50.            handler = fn; 
51.        } 
52. 
53.        if ( type ==="unload" && name !=="one" ) {// 如果是unload事件,则只执行一次
54.            this.one( type, data, fn );// 迭代调用,没有清晰的结构很危险
55.        } else {
56.            for (var i = 0, l =this.length; i < l; i++ ) {
57.                jQuery.event.add( this[i], type, handler, data ); // 遍历匹配的元素,并绑定事件处理函数
58.            } 
59.        } 
60.        // 返回jQuery对象,使得可以继续链式调用
61.        returnthis;
62.    }; 
63.}); 
64.10.4.4  jQuery.event.add 
65..bind()和.one()都调用了jQuery.event.add来实现,为元素elem添加类型types的句柄handler,事实上所有的事件绑定最后都通过jQuery.event.add来实现。其执行过程大致如下:
66.1.  先调用jQuery._data从$.cache中取出已有的事件缓存(私有数据,Cache的解析详见数据缓存)
67.2.  如果是第一次在DOM元素上绑定该类型事件句柄,在DOM元素上绑定jQuery.event.handle,作为统一的事件响应入口
68.3.  将封装后的事件句柄放入缓存中 
69.传入的事件句柄,会被封装到对象handleObj的handle属性上,此外handleObj还会填充guid、type、namespace、data属性;DOM事件句柄elemData.handle指向jQuery.event.handle,即jQuery在DOM元素上绑定事件时总是绑定同样的DOM事件句柄jQuery.event.handle。
70.事件句柄在缓存$.cache中的数据结构如下,事件类型和事件句柄都存储在属性events中,属性handle存放的执行这些事件句柄的DOM事件句柄:
71.elemData = {                                                         
72.    events: {                                                        
73.        'click' : [                                                  
74.            { guid: 5, type: 'click', namespace: '', data: undefined,
75.                handle: { guid: 5, prototype: {} }                   
76.            },                                                       
77.            { ... }                                                  
78.        ],                                                           
79.        'keypress' : [ ... ]                                         
80.    },                                                               
81.    handle: { // DOM事件句柄                                            
82.        elem: elem,                                                  
83.        prototype: {}                                                
84.    }                                                                
85.}                                                                    
86.看看jQuery.event.add的源码解析: 
87.jQuery.event = { 
88.    // Bind an event to an element
89.    // Original by Dean Edwards
90.    // 为元素elem添加类型types的句柄handler
91.    add: function( elem, types, handler, data ) {
92.        // 忽略 Text Comment
93.        if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
94.            return;
95.        } 
96. 
97.        if ( handler ===false ) {// 如果事件处理函数是false,则用returnFalse代替false
98.            handler = returnFalse; // returnFalse会取消事件的默认行为 
99.        } elseif ( !handler ) {
100.            // Fixes bug #7229. Fix recommended by jdalton
101.            return;// 如果handler是undefined null '',则直接返回,不执行后边的代码
102.        } 
103. 
104.        var handleObjIn, handleObj;
105. 
106.        if ( handler.handler ) {// 如果已经是封装过的jQuery事件对象(JS真是愁人啊,弱类型,只能通过特性、属性判断对象的类型)
107.            handleObjIn = handler; // handleObj是DOM事件句柄对象(丰富后的jQuery.event.handle) 
108.            handler = handleObjIn.handler;// handler始终是个函数
109.        } 
110. 
111.        // Make sure that the function being executed has a unique ID
112.        if ( !handler.guid ) {// 如果没有分配唯一id,则分配一个
113.            handler.guid = jQuery.guid++;
114.        } 
115. 
116.        // Init the element's event structure
117.        // 内部数据存储在内部对象上,因此elemData不应该为空,除非elem不支持jQuery缓存(embed、object、applet)
118.        var elemData = jQuery._data( elem );// 仅仅调用_data接口一次,后续直接在返回的对象上操作
119. 
120.        // If no elemData is found then we must be trying to bind to one of the
121.        // banned noData elements
122.        // 如果elemData不存在,说明是一个不支持缓存的元素上绑定事件,直接返回
123.        if ( !elemData ) {
124.            return;
125.        } 
126. 
127.        var events = elemData.events,// 事件类型和事件句柄都存储在属性events中
128.            eventHandle = elemData.handle;// 属性handle存放的执行这些事件句柄的DOM事件句柄
129. 
130.        if ( !events ) {
131.            elemData.events = events = {};// 初始化一个存放事件的对象,事件名为key,事件函数为value
132.        } 
133. 
134.        if ( !eventHandle ) {// 初始化一个执行事件函数的函数
135.            elemData.handle = eventHandle =function( e ) {
136.                // Discard the second event of a jQuery.event.trigger() and
137.                // when an event is called after a page has unloaded
138.                // 调用jQuery.event.handler
139.                // jQuery.event.handler是什么意思?
140.                returntypeof jQuery !=="undefined" && (!e || jQuery.event.triggered !== e.type) ?
141.                    jQuery.event.handle.apply( eventHandle.elem, arguments ) :
142.                    undefined; 
143.            }; 
144.        } 
145. 
146.        // Add elem as a property of the handle function
147.        // This is to prevent a memory leak with non-native events in IE.
148.        // 内存泄漏?
149.        // 将elem作为eventHandle的属性存储,用来避免IE中非本地事件的内存泄漏
150.        eventHandle.elem = elem; 
151. 
152.        // Handle multiple events separated by a space
153.        // jQuery(...).bind("mouseover mouseout", fn);
154.        types = types.split(" ");// 同时绑定的多个事件可以用空格隔开
155.        // 为什么有的地方用字符串直接量,有的地方要用正则呢?因为正则可以进行全局匹配g
156. 
157.        var type, i = 0, namespaces;
158. 
159.        while ( (type = types[ i++ ]) ) {// 又一种遍历数组的方法,故意秀技巧么?
160.            handleObj = handleObjIn ? 
161.                jQuery.extend({}, handleObjIn) :
162.                { handler: handler, data: data };// 创建事件句柄对象,后边还会添加属性:guid anmespace type
163. 
164.            // Namespaced event handlers
165.            // 如果事件字符串中有句号.,则说明有命名空间
166.            if ( type.indexOf(".") > -1 ) {
167.                namespaces = type.split(".");
168.                type = namespaces.shift();// 取出第一个元素
169.                handleObj.namespace = namespaces.slice(0).sort().join(".");// 将于下部分作为命名空间,存储到属性namespace中
170. 
171.            } else {
172.                namespaces = []; 
173.                handleObj.namespace = ""; // 没有命名空间 
174.            } 
175. 
176.            handleObj.type = type; // 事件类型 
177.            if ( !handleObj.guid ) {
178.                handleObj.guid = handler.guid;// 事件对象唯一id
179.            } 
180. 
181.            // Get the current list of functions bound to this event
182.            var handlers = events[ type ],// 事件句柄队列,取出已经存在函数数组
183.                special = jQuery.event.special[ type ] || {};// 特殊处理的事件
184. 
185.            // Init the event handler queue
186.            if ( !handlers ) {// 如果没有绑定事件,则初始化为一个空数组
187.                handlers = events[ type ] = [];//
188. 
189.                // Check for a special event handler
190.                // Only use addEventListener/attachEvent if the special
191.                // events handler returns false
192.                // 如果特殊事件没有setup属性 或 setup返回false,则用浏览器原生的绑定事件接口addEventListener/addEventListener,例如live事件
193.                if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) ===false ) {
194.                    // Bind the global event handler to the element 
195.                    // 绑定事件句柄 
196.                    if ( elem.addEventListener ) { 
197.                        elem.addEventListener( type, eventHandle,false );// jQuery绑定的事件默认都是起泡阶段捕获
198. 
199.                    } else if ( elem.attachEvent ) {
200.                        elem.attachEvent( "on" + type, eventHandle ); // IE事件模型中没有2级DOM事件模型具有的事件捕捉的概念,只有起泡阶段
201.                    } 
202.                } 
203.            } 
204. 
205.            if ( special.add ) {// 如果有add方法,就调用
206.                special.add.call( elem, handleObj );
207. 
208.                if ( !handleObj.handler.guid ) {// 始终保证事件句柄有唯一的id
209.                    handleObj.handler.guid = handler.guid;
210.                } 
211.            } 
212. 
213.            // Add the function to the element's handler list
214.            // 将句柄对象handleObj,加入到句柄数组handler中,
215.            // handleObj包含以下属性:data 唯一guid 命名空间namespace 事件类型type handler函数
216.            handlers.push( handleObj ); 
217. 
218.            // Keep track of which events have been used, for event optimization
219.            // 记录已经使用的事件,用于事件优化?怎么个优化?
220.            jQuery.event.global[ type ] = true; 
221.        } 
222. 
223.        // Nullify elem to prevent memory leaks in IE
224.        // 将elem置为null,避免IE中的内存泄漏(这行代码真是坑爹啊,没有无数次的测试确认,怎么可能想到这行代码呢)
225.        elem = null;
226.    }, 
227.    // ...
228.}; 
229.10.5    .unbind() 
230.10.5.1  如何使用 
231..unbind( [eventType] [, handler(eventObject)] ) 从匹配的元素上删除一个之前绑定的事件句柄
232.如果没有参数,删除所有事件句柄 
233..unbind( eventType, false ) 删除通过.bind( eventType, false )绑定的事件句柄
234..unbind( event )    看不懂。。。 
235.10.5.2  调用过程 
236.jQuery.fn.unbind → jQuery.event.remove 
237.10.5.3  源码分析 
238.jQuery.fn.extend({ 
239.    // 删除句柄:删除一个或多个之前附加的事件句柄,jQuery事件只在起泡阶段捕获,不需要再定义控制捕获阶段的参数
240.    unbind: function( type, fn ) {
241.        // Handle object literals
242.        // 一次删除多个事件句柄,key是事件类型,type[key]是事件句柄(这里是迭代复用)
243.        // !type.preventDefault 表明这不是一个就jQuery事件对象
244.        if (typeof type ==="object" && !type.preventDefault ) {
245.            for (var keyin type ) {
246.                this.unbind(key, type[key]);
247.            } 
248.        // 到这里,type可能是字符串,也可能是jQuery事件对象
249.        } else {
250.            for (var i = 0, l =this.length; i < l; i++ ) {
251.                jQuery.event.remove( this[i], type, fn ); // 调用remove删除句柄
252.            } 
253.        } 
254. 
255.        returnthis;
256.    }, 
257.    // ...
258.}; 
259.10.5.4  jQuery.event.remove 
260..unbind()通过调用jQuery.event.remove,删除之前绑定的一个或多个事件句柄,事实上所有的句柄删除最后都通过jQuery.event.remove实现,其执行过程大致如下:
261.1. 现调用jQuery._data从缓存$.cache中取出elem对应的所有数组(内部数据,与调用jQuery.data存储的数据稍有不同 
262.2. 如果未传入types则移除所有事件句柄,如果types是命名空间,则移除所有与命名空间匹配的事件句柄
263.3. 如果是多个事件,则分割后遍历 
264.4. 如果未指定删除哪个事件句柄,则删除事件类型对应的全部句柄,或者与命名空间匹配的全部句柄 
265.5. 如果指定了删除某个事件句柄,则删除指定的事件句柄     
266.6. 所有的事件句柄删除,都直接在事件句柄数组jQuery._data( elem ).events[ type ]上调用splice操作 
267.7. 最后检查事件句柄数组的长度,如果为0,或为1但要删除,则移除绑定在elem上DOM事件 
268.8. 最后的最后,如果elem对应的所有事件句柄events都已删除,则从缓存中移走elem的内部数据
269.9. 在以上的各个过程,都要检查是否有特例需要处理  
270.看看它的源码分析: 
271.jQuery.event = { 
272.    // ...
273.    /**
274.     * 移除元素elem上的一个或多个或一组事件
275.     * pos 指要移除的是第几个types事件,可以减少遍历次数
276.     * handler的属性guid唯一标识某个事件对象,移除时通过比较guid是否相等
277.     */ 
278.    remove: function( elem, types, handler, pos ) {// add/remove都是作为jQuery内部接口使用
279.        // don't do events on text and comment nodes
280.        // 忽略文本元素Text和注释元素Comment
281.        if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
282.            return;
283.        } 
284. 
285.        if ( handler ===false ) {// 替换布尔型handler为函数,保证handler始终是一个函数,使得remove接口的使用更加便捷
286.            handler = returnFalse; 
287.        } 
288. 
289.        var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType,
290.            elemData = jQuery.hasData( elem ) && jQuery._data( elem ),// 如果在缓存cache中有数据,则取出,否则elemData为undefine
291.            // 因为jQuery._data总是返回一个对象,因此要先判断缓存cache中是否有数组
292.            events = elemData && elemData.events;// 取出事件句柄数组,用正则表达式可以将多行代码合并为一行,这里可以用if-else或三元表达式代替
293. 
294.        if ( !elemData || !events ) {// 如果未绑定,则忽略本次调用,直接返回
295.            return;
296.        } 
297. 
298.        // types is actually an event object here
299.        // 如果types是一个jQuery Event对象,则。。。
300.        if ( types && types.type ) {
301.            handler = types.handler; 
302.            types = types.type; 
303.        } 
304. 
305.        // Unbind all events for the element
306.        // 如果types为false(强制转换类型),则移除所有事件
307.        // 如果types是字符串,并且以句号开头,则移除types命名空间下的指定事件(type+types)
308.        if ( !types ||typeof types ==="string" && types.charAt(0) ==="." ) {
309.            types = types || ""; 
310. 
311.            for ( typein events ) {// 遍历events,移除所有已绑定的事件,这里是迭代调用,jQuery的源码很精致,能复用的代码会尽量复用
312.                jQuery.event.remove( elem, type + types );// 全部事件,或某一命名空间下的全部事件
313.            } 
314. 
315.            return;
316.        } 
317. 
318.        // Handle multiple events separated by a space
319.        // jQuery(...).unbind("mouseover mouseout", fn);
320.        // 同时移除多个事件,事件之间用空格隔开,使得remove接口很灵活
321.        types = types.split(" ");
322. 
323.        // 个人觉的变量i的定义与使用离得那么远,不是好习惯,像这种局部使用的变量,应该哪里用就在哪里定义
324.        // 可能作者觉得集中定义显得代码不凌乱吧
325.        while ( (type = types[ i++ ]) ) {// 有一种循环方式
326.            origType = type; 
327.            handleObj = null; 
328.            all = type.indexOf(".") < 0;// all在这里表示移除的是type命名空间下的全部事件对象,小于0表示不是以.开头
329.            // all为true表示是事件,false表示命名空间
330.            namespaces = []; 
331. 
332.            if ( !all ) {// 如果以.开头,即type是命名空间,构建命名空间正则表达式
333.                // Namespaced event handlers
334.                namespaces = type.split(".");// 全部命名空间
335.                type = namespaces.shift();// 取出第一个作为事件类型,其余的为命名空间
336. 
337.                namespace = new RegExp("(^|\\.)" + 
338.                    jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("
\\.(?:.*\\.)?") +"(\\.|$)");
339.                // 这里要动态创建正则,因此用了 new RegExp 的方式
340.            } 
341. 
342.            eventType = events[ type ]; // 取出type对应的事件对象数组 
343. 
344.            if ( !eventType ) {// 如果type对应的事件对象数组不存在,则跳过本次循环(既然不存在就不必移除了)
345.                continue;
346.            } 
347. 
348.            if ( !handler ) {// 没有指定移除哪个事件对象,handler其实是一个带有guid属性的函数,因为通过guid可以找到对应的事件对象,因此这里依然称handler为事件对象
349.                for ( j = 0; j < eventType.length; j++ ) {// 遍历事件对象数组,这里因为是遍历一个动态的数组,所以没有定义eventType.length变量
350.                    handleObj = eventType[ j ];
351. 
352.                    if ( all || namespace.test( handleObj.namespace ) ) { // 如果是事件或命名空间匹配
353.                        jQuery.event.remove( elem, origType, handleObj.handler, j );// 取出事件对象,迭代调用remove接口
354.                        // handleObj.handler有guid属性,因此迭代调用remove依然可以匹配到对应handleObj,事实上这里也可以改写为传入handleObj
355.                        // 注意最后一个参数j,remove的pos不为null,就不会在其他地方被splice删除,所以下一行才会splice(这个问题的处理太乱了)
356.                        eventType.splice( j--, 1 );// 如果循环遍历的是一个变化的数组,则可以用这种方式:j++之前先执行j--,保证不会因为数组下标的错误导致某些数组元素遍历不到,秀!
357.                    } 
358.                } 
359.                // 如果未指定删除哪个事件句柄,则删除事件类型对应的全部句柄,或者与命名空间匹配的全部句柄
360.                // 这里对$.cache的操作,是通过对 eventType = jQuery._data( elem ).events[ type ]的直接操作进行维护,直接操作事件句柄数组
361.                // 如果是我,可能会再开发一个接口供客户端进行这种微操,其实没必要。
362.                continue;
363.            } 
364. 
365.            // 到这里,说明指定事件句柄handler
366.             
367.            special = jQuery.event.special[ type ] || {};// 特例
368. 
369.            // 如果没有指定pos,则默认从0开始遍历,这里的pos可以减少遍历次数,提高性能,多么精致的代码
370.            // 本人喜欢这种极致的代码,更喜欢优雅的代码,更注重代码的可读性,稍微的性能下降可以接受
371.            for ( j = pos || 0; j < eventType.length; j++ ) {
372.                handleObj = eventType[ j ];
373. 
374.                if ( handler.guid === handleObj.guid ) {// 比较事件函数的guid和时间对象的guid,===可以避免类型转换,稍微提高性能
375.                    // remove the given handler for the given type 
376.                    if ( all || namespace.test( handleObj.namespace ) ) { 
377.                        if ( pos == null ) { // 如果没有指定pos,匹配到后就删除 
378.                            eventType.splice( j--, 1 );// j++之前先j--,避免数组变化导致遍历错误
379.                        } 
380. 
381.                        if ( special.remove ) { // 如果有自定义的remove方法,则调用(似乎只有live有add remove方法)
382.                            special.remove.call( elem, handleObj );
383.                        } 
384.                    } 
385.                    // 因为guid是全局唯一的,所以匹配到guid对应的事件就可以退出了 
386.                    if ( pos != null ) { // 如果pos不为null,说明是要删除指定的事件对象,任务完成,退出 
387.                        break; 
388.                    } 
389.                } 
390.            } 
391. 
392.            // remove generic event handler if no more handlers exist
393.            // 如果type对应的事件对象数组为空,或者发现只剩一个,则移除绑定在elem上浏览器原生事件
394.            if ( eventType.length === 0 || pos !=null && eventType.length === 1 ) {
395.                // 检查special是否有自定义的teardown,优先调用
396.                // 这一行的巧妙支出在于:
397.                // 1. special不会为null或undefined,如果没有找到会被赋值为{},是空设置模式的应用
398.                // 2. 首先巧妙的判断special.teardown是否存在,如果不存在则认为是普通事件,如果存在则可以调用teardown
399.                // 3. 在jQuery源码中你看到过没有返回值的函数码?似乎没有,teardown返回false表示失败,还得用普通方式删除
400.                if ( !special.teardown || special.teardown.call( elem, namespaces ) ===false ) { 
401.                    jQuery.removeEvent( elem, type, elemData.handle );
402.                } 
403. 
404.                ret = null; // 这个变量搞笑了,自从定义之后就从来就没用过,明显多余,可以遗留代码里的变量吧
405.                delete events[ type ];// 删除类型属性,还是直接对数组句柄数组操作,根本没有什么缓存微操接口
406.            } 
407.        } 
408. 
409.        // Remove the expando if it's no longer used
410.        // 从jQuery.cache中移除elem的数据,最后检查elemData.events
411.        if ( jQuery.isEmptyObject( events ) ) {
412.            var handle = elemData.handle;
413.            if ( handle ) {
414.                handle.elem = null; // 置空,删除对HTML元素的引用,避免因垃圾回收机制不起作用导致的浏览器内存泄漏
415.                // 这是一个非常好的技巧,我们在JavaScript中引用完HTML元素后一定要置空。
416.            } 
417. 
418.            delete elemData.events;// 删除存储事件句柄的对象
419.            delete elemData.handle;// 删除DOM事件句柄
420. 
421.            if ( jQuery.isEmptyObject( elemData ) ) {// 都删了难道还不是isEmptyObject?有点多余,除非某些浏览器不支持delete,可问题是不支持也得移除数据,还是多余!
422.                jQuery.removeData( elem, undefined,true );// 彻底的从缓存中移走elem的内部数据(存储在属性expando上)
423.            } 
424.        } 
425.    }, 
426. 
427.    // ...
428.}; 
429.10.6    .live() .die() 
430.10.6.1  如何使用 
431.live 在匹配当前选择器的元素上绑定一个事件处理函数,包括已经存在的和未来添加的,即任何添加的元素只要匹配当前选择器,就会被绑定事件处理函数
432..live( eventType, handler ) 在匹配当前选择器的元素上绑定一个指定类型的事件处理函数
433..live( eventType, eventData, handler )  eventData可以传递数据给事件处理函数
434..live( events ) 绑定多个事件 
435..die()  删除之前通过.live()绑定的全部事件句柄 
436..die( eventType [, handler] )   eventType是字符串事件类型,删除一个或一类之前通过.live()绑定的事件句柄
437..die( eventTypes )  eventTypes是Map对象,删除多类事件句柄
438.10.6.2  调用过程 
439. 
440..live()方法永远不会将事件绑定到匹配的元素上,而是将事件绑定到祖先元素(document或context),由该祖先元素代理子元素的事件。
441.以 $('.clickme').live('click',function() { … } ) 为例,当在新添加的元素上点击时,执行过程如下:
442.1. click事件被生成,并传递给

,待
处理            
443.2. 因为
没有直接绑定click事件,因此事件沿着DOM树进行冒泡传递          
444.3. 事件冒泡传递直到到达DOM树的根节点,在根节点上绑定了.live()指定的原始事件处理函数(在1.4以后的版本中,.live()绑定事件到上下文context,以提升性能) 
445.4. 执行.live()指定的事件处理函数  
446.5. 原始事件处理函数检查event的target属性以确定是否继续执行,检查的方式是 $(event.target).closes
447.6. 如果匹配,则原始事件处理函数执行,上下位被设置为找到的元素                                                                  
448.因为第5步的检查是在执行原始事件处理函数之前,因此元素可以在任何时候添加,并且事件可以生效
449.调用过程: 
450.$('div').live('click',function(){ alert(1); } )
451.jQuery.fn.live → jQuery.event.add → jQuery._data → jQuery.event.special.live.add → jQuery.event.add
452.$('div').die('click',function(){ alert(1); } )
453.jQuery.fn.die → jQuery.fn.unbind → jQuery.event.remove jQuery._data
454.调试过程中发现个有趣的现象:在父节点上绑定live事件,而不是具体的时间类型? 
455.  
456.10.6.3  源码分析 
457.看看.live()/.die()的源码分析: 
458.// 事件类型修正
459.var liveMap = {
460.    focus: "focusin",
461.    blur: "focusout",
462.    mouseenter: "mouseover",
463.    mouseleave: "mouseout"
464.}; 
465.// die 移除用live附加的一个或全部事件处理函数
466.// 对应关系:bind-unbind, live-die, delegate-undelegate
467./**
468.* live 在匹配当前选择器的元素上绑定一个事件处理函数,包括已经存在的,和未来添加的,即任何添加的元素只要匹配当前选择器就会被绑定事件处理函数
469.* .live( eventType, handler ) 在匹配当前选择器的元素上绑定一个指定类型的事件处理函数
470.* .live( eventType, eventData, handler ) eventData可以传递给事件处理函数
471.* .live( events ) 绑定多个事件
472.*
473.*
474.* 参考文档:
475.* http://api.jquery.com/live
476.* http://www.codesky.net/article/doc/201004/20100417042278.htm
477.* http://hi.baidu.com/silveringsea/blog/item/55cd25016ecde30c1c958341.html
478.*
479.* 如何使用
480.* 示例
481.* 局限
482.* 修正
483.*
484.* live被翻译为鲜活绑定、延迟绑定(更准确些)
485.* live通过委派,而不是在匹配元素上直接绑定事件来实现
486.*
487.* 官方文档翻译:事件代理
488.* .live()方法能对尚未添加到DOM文档中的元素生效。
489.* .live()方法永远不会将事件绑定到匹配的元素上,而是将事件绑定到祖先元素(document或context),由该祖先元素代理子元素的事件。  *
490.* 以 $('.clickme').live('click', function() { … } ) 为例,当在新添加的元素上点击时,执行过程如下:                  
491.* 1. click事件被生成,并传递给
,待
处理
492.* 2. 因为
没有直接绑定click事件,因此事件沿着DOM树进行冒泡传递
493.* 3. 事件冒泡传递直到到达DOM树的根节点,在根节点上绑定了.live()指定的原始事件处理函数(在1.4以后的版本中,.live()绑定事件到上下文context,以提升性能)
494.* 4. 执行.live()指定的事件处理函数
495.* 5. 原始事件处理函数检查event的target属性以确定是否继续执行,检查的方式是 $(event.target).closest(".clickme")
496.* 6. 如果匹配,则原始事件处理函数执行,上下位被设置为找到的元素
497.*
498.* 因为第5步的检查是在执行原始事件处理函数之前,因此元素可以在任何时候添加,并且事件可以生效
499.*
500.*/ 
501.jQuery.each(["live","die"],function( i, name ) {
502.    jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) {
503.        var type, i = 0, match, namespaces, preType,
504.            selector = origSelector || this.selector, // 选择器表达式 
505.            // 很巧妙,如果没有origSelector,那么采用当前jQuery对象的选择器,、
506.            // 可是,如果origSelector为true,后边的处理对象就变成了以origSelector为选择器的jQuery对象
507.            context = origSelector ? this : jQuery( this.context );// 上下文,
508.            // 这里也是,如果没有origSelector,那么上下文就变成当前jQuery对象的上下文
509.            // 可是,如果origSelector为true(类型转换),当前jQuery对象就变成上下文了
510.            // 就是在这里,通过一个内部变量,改变了选择器表达式和上下分,区分开了live/die和delegate/undelegate,用同样的代码实现了两种接口
511. 
512.        // 一次绑定或删除多个事件
513.        if (typeof types ==="object" && !types.preventDefault ) {// types不是jQuery事件对象
514.            for (var keyin types ) {// 迭代复用
515.                context[ name ]( key, data, types[key], selector );
516.            } 
517. 
518.            returnthis;
519.        } 
520. 
521.        // 如果是die,移除origSelector匹配元素上的所有通过live绑定的事件
522.        // 没有指定事件类型 + origSelector + origSelector是CSS选择器
523.        if ( name ==="die" && !types &&
524.                    origSelector && origSelector.charAt(0) ==="." ) {
525. 
526.            context.unbind( origSelector );// 事实上bind/unbind是基础,add/remove/tigger/handle是所有事件的基础
527. 
528.            returnthis;
529.        } 
530.         
531.        // 修正参数(如果data为false或没有传入data参数)
532.        // 完整:types, data, fn, origSelector
533.        // types, false, fn, origSelector
534.        // 或 types, fn, origSelector
535.        if ( data ===false || jQuery.isFunction( data ) ) {
536.            fn = data || returnFalse; 
537.            data = undefined; 
538.        } 
539. 
540.        types = (types || "").split(" "); // 多个事件用空格隔开
541. 
542.        while ( (type = types[ i++ ]) !=null ) {// 遍历事件类型数组
543.            match = rnamespaces.exec( type );// 取到第一个.后的命名空间
544.            namespaces = ""; 
545. 
546.            if ( match )  {// 如果有命名空间,即事件类型后还有.
547.                namespaces = match[0]; // .+命名空间 
548.                type = type.replace( rnamespaces,"" );// 过滤.+命名空间,初学者会尝试用indexOf查找.的位置,然后截取
549.            } 
550. 
551.            if ( type ==="hover" ) {// 将hover分解为mouseenter mouseleave,继续遍历
552.                types.push( "mouseenter" + namespaces, "mouseleave" + namespaces );
553.                continue;
554.            } 
555. 
556.            preType = type; //  
557. 
558.            if ( liveMap[ type ] ) {// 修正事件名名,修正其实不准确,比如遇到focus,绑定了两个:focus focusin,不会造成两次事件响应么?
559.                types.push( liveMap[ type ] + namespaces );// 将修正后的事件类型入队,因为用了while循环,所有不必担心遍历动态数组的问题
560.                type = type + namespaces; // 再恢复type,包含了命名空间,这不是蛋疼么,把命名空间去掉只为了检测hover和是否需要修正? 
561. 
562.            } else {
563.                type = (liveMap[ type ] || type) + namespaces;// 修正事件名,这里可以直接写成:type + namespaces
564.                // 这行的逻辑有点问题,已经知道liveMap[ type ]是false了,这个地方的代码有些重复
565.            } 
566.            /**
567.             * 上边的if-else改写为:
568.             * if ( liveMap[ type ] ) types.push( liveMap[ type ] + namespaces )
569.             * type = type + namespaces
570.             */
571. 
572.            // 这里开始live的特殊处理,虽说live/die的逻辑比较接近,再加上delegate/undelegate,复用的粒度小但是很精髓
573.            // 同时为了减少接口,并没有将公共部分提取出来,总的来说,live/die这段代码出来的还是刚刚好
574.            if ( name ==="live" ) {
575.                // bind live handler
576.                for (var j = 0, l = context.length; j < l; j++ ) {
577.                    // 绑定到上下文,事件类型经过liveConvert后变为 live.type.selector 
578.                    // 前边做了那么多铺垫,这行才是关键! 
579.                    // add: function( elem, types, handler, data ) { 
580.                    // context[j] 如果有origSelector则是当前jQuery对象,如果没有则是当前jQuery对象的上下文 
581.                    // live事件的格式比较特殊,应该trigger里会有live的特殊处理,live的处理在特例special里 
582.                    jQuery.event.add( context[j],"live." + liveConvert( type, selector ),
583.                        { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } );
584.                } 
585. 
586.            } else {
587.                // unbind live handler
588.                // 删除live绑定的事件句柄
589.                context.unbind( "live." + liveConvert( type, selector ), fn ); 
590.            } 
591.        } 
592. 
593.        returnthis;
594.    }; 
595.}); 
596./**
597.* live执行句柄,live绑定的事件调用链:add.eventHandle → handler function liveHandler → _data → handler
598.* @param event
599.* @return
600.*/ 
601.function liveHandler( event ) {
602.    var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret,
603.        elems = [], 
604.        selectors = [], 
605.        events = jQuery._data( this, "events" ); 
606. 
607.    // Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911)
608.    if ( event.liveFired ===this || !events || !events.live || event.target.disabled || event.button && event.type ==="click" ) { 
609.        return;
610.    } 
611. 
612.    if ( event.namespace ) {
613.        namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") +"(\\.|$)");
614.    } 
615. 
616.    event.liveFired = this; 
617. 
618.    var live = events.live.slice(0);// 返回一个新的数组,而不改变原来的数组
619. 
620.    for ( j = 0; j < live.length; j++ ) {
621.        handleObj = live[j]; 
622. 
623.        if ( handleObj.origType.replace( rnamespaces,"" ) === event.type ) {
624.            selectors.push( handleObj.selector );
625. 
626.        } else {
627.            live.splice( j--, 1 ); 
628.        } 
629.    } 
630. 
631.    match = jQuery( event.target ).closest( selectors, event.currentTarget );
632. 
633.    for ( i = 0, l = match.length; i < l; i++ ) {
634.        close = match[i]; 
635. 
636.        for ( j = 0; j < live.length; j++ ) {
637.            handleObj = live[j]; 
638. 
639.            if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) && !close.elem.disabled ) {
640.                elem = close.elem; 
641.                related = null; 
642. 
643.                // Those two events require additional checking
644.                if ( handleObj.preType ==="mouseenter" || handleObj.preType ==="mouseleave" ) {
645.                    event.type = handleObj.preType;
646.                    related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0];
647. 
648.                    // Make sure not to accidentally match a child element with the same selector
649.                    if ( related && jQuery.contains( elem, related ) ) { 
650.                        related = elem; 
651.                    } 
652.                } 
653. 
654.                if ( !related || related !== elem ) {
655.                    elems.push({ elem: elem, handleObj: handleObj, level: close.level });
656.                } 
657.            } 
658.        } 
659.    } 
660. 
661.    for ( i = 0, l = elems.length; i < l; i++ ) {
662.        match = elems[i]; 
663. 
664.        if ( maxLevel && match.level > maxLevel ) {
665.            break;
666.        } 
667. 
668.        event.currentTarget = match.elem;
669.        event.data = match.handleObj.data;
670.        event.handleObj = match.handleObj;
671. 
672.        ret = match.handleObj.origHandler.apply( match.elem, arguments );
673. 
674.        if ( ret ===false || event.isPropagationStopped() ) {
675.            maxLevel = match.level; 
676. 
677.            if ( ret ===false ) {
678.                stop = false; 
679.            } 
680.            if ( event.isImmediatePropagationStopped() ) {
681.                break;
682.            } 
683.        } 
684.    } 
685. 
686.    return stop;
687.} 
688.// 在live事件类型type后增加上selector,作为命名空间
689.function liveConvert( type, selector ) {
690.    // . > `
691.    // 空格 > &
692.    // 在type后增加上selector,作为命名空间
693.    return (type && type !=="*" ? type +"." :"") + selector.replace(rperiod,"`").replace(rspaces,"&");// 
694.} 
695.10.7    .deletege() .undelegate() 
696.10.7.1  如何使用 
697..delegate( selector, eventType, handler )   在匹配选择器表达式selector的元素上,将一个事件句handler柄绑定到一个或多个事件上
698.无论是现有的还是将来添加的,基于一组特定的根元素(即上下文) 
699..delegate( selector, eventType, eventData, handler )    Map型对象eventData的属性将被复制到事件句柄handler
700..delegate( selector, events )   绑定一个或多个事件句柄
701..undelegate()   在当前jQuery对象上,移除所有live事件句柄
702..undelegate( selector, eventType, handler ) 在当前jQuery对象上,移除live. eventType.selector事件句柄
703.为当前选择所匹配的所有元素移除一个事件处理程序,现在或将来,基于一组特定的根元素。
704..undelegate( selector, events ) 在当前jQuery对象上,移除多个live事件句柄,events是Map型对象,key
705..undelegate( namespace )    在当前jQuery对象上,移除命名空间匹配的live事件句柄
706.从接口的含义看,.deletege()/.undelegate()与.live()/.die()的区别在于绑定事件时的上下文不同,上下文即事件绑定到的元素:
707..deletege()/.undelegate()   当前jQuery对象 
708..live()/.die()  当前jQuery对象的上下文,经常是document,也可以是构造jQuery对象时指定的上下文
709. 
710.10.7.2  调用过程 
711.jQuery.fn.delegate → jQuery.fn.live → jQuery.event.add
712.jQuery.fn.undelegate → jQuery.fn.unbind/die
713.10.7.3  源码分析 
714.// 事件代理,调用live方法实现
715.delegate: function( selector, types, data, fn ) {
716.    // 因为传递了参数selector,live时的上下文变为this,而不是this.context
717.    returnthis.live( types, data, fn, selector );
718.}, 
719.// 删除事件代理,调用unbind或die实现
720.undelegate: function( selector, types, fn ) {
721.    if ( arguments.length === 0 ) {
722.        returnthis.unbind("live" );// 在当前jQuery对象上直接unbind,不需要在去查找上下文
723. 
724.    } else {
725.        // jQuery.fn[ die ] = function( types, data, fn, origSelector /* Internal Use Only */ ) {
726.        returnthis.die( types,null, fn, selector );// 带有选择器字符串,改变了die里的上下文context变量
727.    } 
728.}, 
729.本节小节: 
730.1.  .bind()/.unbind()高级接口的基础,add/remove/tigger/handle是所有的基础,最后才是调用浏览器原生接口
731.2.  .deletege()/.undelegate()与.live()/.die()的区别在于绑定事件时的父节点不同
732.原文链接:
http://nuysoft.iteye.com/blog/1199674
 

你可能感兴趣的:(JQuery)