jQuery之.on()方法  

jQuery之.on()方法
 

还不是完全清楚如何使用.on()进行jQuery事件绑定的同学先看这里http://api.jquery.com/on/

jQuery绑定事件的方法有几种,推荐使用.on()方法绑定,原因有两点:

1.on()方法可以绑定动态添加到页面元素的事件

比如动态添加到页面的DOM元素,用.on()方法绑定的事件不需要关心注册该事件的元素何时被添加进来,也不需要重复绑定。有的同学可能习惯于用.bind()、.live()或.delegate(),查看源码就会发现,它们实际上调用的都是.on()方法,并且.live()方法在jQuery1.9版本已经被移除。

1
2
3
4
5
6
7
8
9
10
11
12
bind: function( types, data, fn ) {
    return this.on( types, null, data, fn );
},
 
live: function( types, data, fn ) {
    jQuery( this.context ).on( types, this.selector, data, fn );
    return this;
},
 
delegate: function( selector, types, data, fn ) {
    return this.on( types, selector, data, fn );
}

移除.on()绑定的事件用.off()方法。

2.on()方法绑定事件可以提升效率

很多文章都提到了利用事件冒泡和代理来提升事件绑定的效率,大多都没列出具体的差别,所以为了求证,我做一个小测试。

假设页面添加了5000个li,用chrome开发者工具Profiles测试页面载入时间。

普通绑定(姑且这么称呼它)

1
2
3
$('li').click(function(){
    console.log(this)
});

绑定过程的执行时间

jQuery之.on()方法
 

普通绑定相当于在5000li上面分别注册click事件,内存占用约4.2M,绑定时间约为72ms。

.on()绑定

1
2
3
$(document).on('click', 'li', function(){
    console.log(this)
})

绑定过程的执行时间

jQuery之.on()方法
 

.on()绑定利用事件代理,只在document上注册了一个click事件,内存占用约2.2M,绑定时间约为1ms。

.on()源码分析

.on()方法分析包含其调用的两个主要方法: 
.add()进行事件注册

.dispatch()进行事件代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
/* jQuery 1.10.2 */
on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
    var type, origFn;
 
    // Types can be a map of types/handlers
    if ( typeof types === "object" ) {
        // ( types-Object, selector, data )
        if ( typeof selector !== "string" ) {
            // ( types-Object, data )
            data = data || selector;
            selector = undefined;
        }
        // 遍历types对象,针对每一个属性绑定on()方法
        // 将types[type]作为fn传入
        for ( type in types ) {
            this.on( type, selector, data, types[ type ], one );
        }
        return this;
    }
 
    // 参数修正
    // jQuery这种参数修正的方法很好
    // 可以兼容多种参数形式
    // 可见在灵活调用的背后做了很多处理
    if ( data == null && fn == null ) {
        // ( types, fn )
        fn = selector;
        data = selector = undefined;
    } else if ( fn == null ) {
        if ( typeof selector === "string" ) {
            // ( types, selector, fn )
            fn = data;
            data = undefined;
        } else {
            // ( types, data, fn )
            fn = data;
            data = selector;
            selector = undefined;
        }
    }
    if ( fn === false ) {
        // fn传入false时,阻止该事件的默认行为
        // function returnFalse() {return false;}
        fn = returnFalse;
    } else if ( !fn ) {
        return this;
    }
 
    // one()调用on()
    if ( one === 1 ) {
        origFn = fn;
        fn = function( event ) {
            // Can use an empty set, since event contains the info
            // 用一个空jQuery对象,这样可以使用.off方法,
            // 并且event带有remove事件需要的信息
            jQuery().off( event );
            return origFn.apply( this, arguments );
        };
        // Use same guid so caller can remove using origFn
        // 事件删除依赖于guid
        fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
    }
 
    // 这里调用jQuery的each方法遍历调用on()方法的jQuery对象
    // 如$('li').on(...)则遍历每一个li传入add()
    // 推荐使用$(document).on()或者集合元素的父元素
    return this.each( function() {
        jQuery.event.add( this, types, fn, data, selector );
    });
},
 
// 事件注册
add: function( elem, types, handler, data, selector ) {
    var tmp, events, t, handleObjIn,
        special, eventHandle, handleObj,
        handlers, type, namespaces, origType,
        elemData = jQuery._data( elem );
 
    // Don't attach events to noData or
    // text/comment nodes (but allow plain objects)
    // 不符合绑定条件的节点
    if ( !elemData ) {
        return;
    }
 
    // Caller can pass in an object of custom data in lieu of the handler
    // 传入的handler为事件对象
    if ( handler.handler ) {
        handleObjIn = handler;
        handler = handleObjIn.handler;
        selector = handleObjIn.selector;
    }
 
    // Make sure that the handler has a unique ID,
    // used to find/remove it later
    // 为handler分配一个ID,用于之后的查找或删除
    if ( !handler.guid ) {
        handler.guid = jQuery.guid++;
    }
 
    // Init the element's event structure and main handler,
    // if this is the first
    // 初始化events结构
    if ( !(events = elemData.events) ) {
        events = elemData.events = {};
    }
    if ( !(eventHandle = elemData.handle) ) {
        eventHandle = elemData.handle = function( e ) {
            // Discard the second event of a jQuery.event.trigger() and
            // when an event is called after a page has unloaded
            return typeof jQuery !== core_strundefined &&
            (!e || jQuery.event.triggered !== e.type) ?
                jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
                undefined;
        };
        // Add elem as a property of the handle fn
        // to prevent a memory leak with IE non-native events
        // 添加elem为eventHandle的属性,防止IE非本地事件的内存泄露?
        // 搜索整个源码,只有110行用到了eventHandle.elem
        eventHandle.elem = elem;
    }
 
    // Handle multiple events separated by a space
    // 处理多个以空格分隔的事件类型
    types = ( types || "" ).match( core_rnotwhite ) || [""];
    t = types.length;
    while ( t-- ) {
        tmp = rtypenamespace.exec( types[t] ) || [];
        type = origType = tmp[1];
        // 存储所有命名空间
        namespaces = ( tmp[2] || "" ).split( "." ).sort();
 
        // There *must* be a type, no attaching namespace-only handlers
        if ( !type ) {
            continue;
        }
 
        // If event changes its type,
        // use the special event handlers for the changed type
        // 对于改变了事件类型的特殊事件
        special = jQuery.event.special[ type ] || {};
 
        // If selector defined, determine special event api type,
        // otherwise given type
        type = ( selector ? special.delegateType : special.bindType ) || type;
 
        // Update special based on newly reset type
        special = jQuery.event.special[ type ] || {};
 
        // handleObj is passed to all event handlers
        handleObj = jQuery.extend({
            type: type,
            origType: origType,
            data: data,
            handler: handler,
            guid: handler.guid,
            selector: selector,
            needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
            namespace: namespaces.join(".")
        }, handleObjIn );
 
        // Init the event handler queue if we're the first
        // 初始化handler队列,只初始化一次
        if ( !(handlers = events[ type ]) ) {
            handlers = events[ type ] = [];
            handlers.delegateCount = 0;
 
            // Only use addEventListener/attachEvent
            // if the special events handler returns false
            if ( !special.setup ||
            special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
                // Bind the global event handler to the element
                // 二级DOM事件/IE事件模型
                // eventHandle会调用jQuery.event.dispatch进行事件代理
                if ( elem.addEventListener ) {
                    elem.addEventListener( type, eventHandle, false );
 
                } else if ( elem.attachEvent ) {
                    elem.attachEvent( "on" + type, eventHandle );
                }
            }
        }
 
        if ( special.add ) {
            special.add.call( elem, handleObj );
 
            if ( !handleObj.handler.guid ) {
                handleObj.handler.guid = handler.guid;
            }
        }
 
        // Add to the element's handler list, delegates in front
        if ( selector ) {
            handlers.splice( handlers.delegateCount++, 0, handleObj );
        } else {
            handlers.push( handleObj );
        }
 
        // Keep track of which events have ever been used,
        // for event optimization
        // 跟踪每个事件是否被使用过,为了事件优化
        jQuery.event.global[ type ] = true;
    }
 
    // Nullify elem to prevent memory leaks in IE
    // 将变量置空,防止循环引用导致IE内存泄露
    elem = null;
},
 
// 事件代理
dispatch: function( event ) {
 
    // Make a writable jQuery.Event from the native event object
    // jQuery定义的event对象,兼容标准事件模型与IE事件模型
    event = jQuery.event.fix( event );
 
    var i, ret, handleObj, matched, j,
        handlerQueue = [],
        args = core_slice.call( arguments ),
        handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [],
        special = jQuery.event.special[ event.type ] || {};
 
    // Use the fix-ed jQuery.Event rather than the (read-only) native event
    // 使用jQuery.Event代替浏览器的event
    args[0] = event;
    // 事件的代理节点,比如document
    event.delegateTarget = this;
 
    // Call the preDispatch hook for the mapped type,
    // and let it bail if desired
    if ( special.preDispatch &&
        special.preDispatch.call( this, event ) === false ) {
        return;
    }
 
    // Determine handlers
    // 遍历事件发生节点至代理节点之间的所有节点
    // 匹配每一个发生节点=?绑定节点
    handlerQueue = jQuery.event.handlers.call( this, event, handlers );
 
    // Run delegates first; they may want to stop propagation beneath us
    i = 0;
    // 遍历匹配的节点,并且没有被阻止冒泡
    while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
        event.currentTarget = matched.elem;
 
        j = 0;
        while ( (handleObj = matched.handlers[ j++ ]) &&
         !event.isImmediatePropagationStopped() ) {
 
            // Triggered event must either 1) have no namespace, or
            // 2) have namespace(s) a subset or equal to those
            // in the bound event (both can have no namespace).
            if ( !event.namespace_re ||
             event.namespace_re.test( handleObj.namespace ) ) {
 
                event.handleObj = handleObj;
                event.data = handleObj.data;
 
                // 传入绑定事件的具体节点,调用事件发生函数
                ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle ||
                 handleObj.handler )
                        .apply( matched.elem, args );
 
                if ( ret !== undefined ) {
                    if ( (event.result = ret) === false ) {
                        event.preventDefault();
                        event.stopPropagation();
                    }
                }
            }
        }
    }
 
    // Call the postDispatch hook for the mapped type
    if ( special.postDispatch ) {
        special.postDispatch.call( this, event );
    }
 
    return event.result;
}