還不是完全清楚如何使用.on()進行jQuery事件綁定的同學先看這裡http://api.jquery.com/on/
jQuery綁定事件的方法有幾種,推薦使用.on()方法綁定,原因有兩點:
比如動态添加到頁面的DOM元素,用.on()方法綁定的事件不需要關心注冊該事件的元素何時被添加進來,也不需要重複綁定。有的同學可能習慣于用.bind()、.live()或.delegate(),檢視源碼就會發現,它們實際上調用的都是.on()方法,并且.live()方法在jQuery1.9版本已經被移除。
1
2
3
4
5
6
7
8
9
10
11
12
<code>bind:</code><code>function</code><code>( types, data, fn ) {</code>
<code> </code><code>return</code> <code>this</code><code>.on( types,</code><code>null</code><code>, data, fn );</code>
<code>},</code>
<code>live:</code><code>function</code><code>( types, data, fn ) {</code>
<code> </code><code>jQuery(</code><code>this</code><code>.context ).on( types,</code><code>this</code><code>.selector, data, fn );</code>
<code> </code><code>return</code> <code>this</code><code>;</code>
<code>delegate:</code><code>function</code><code>( selector, types, data, fn ) {</code>
<code> </code><code>return</code> <code>this</code><code>.on( types, selector, data, fn );</code>
<code>}</code>
移除.on()綁定的事件用.off()方法。
很多文章都提到了利用事件冒泡和代理來提升事件綁定的效率,大多都沒列出具體的差别,是以為了求證,我做一個小測試。
假設頁面添加了5000個li,用chrome開發者工具Profiles測試頁面載入時間。
普通綁定(姑且這麼稱呼它)
<code>$(</code><code>'li'</code><code>).click(</code><code>function</code><code>(){</code>
<code> </code><code>console.log(</code><code>this</code><code>)</code>
<code>});</code>
綁定過程的執行時間
普通綁定相當于在5000li上面分别注冊click事件,記憶體占用約4.2M,綁定時間約為72ms。
.on()綁定
<code>$(document).on(</code><code>'click'</code><code>,</code><code>'li'</code><code>,</code><code>function</code><code>(){</code>
<code>})</code>
.on()綁定利用事件代理,隻在document上注冊了一個click事件,記憶體占用約2.2M,綁定時間約為1ms。
.on()方法分析包含其調用的兩個主要方法:
.add()進行事件注冊
.dispatch()進行事件代理
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
<code>/* jQuery 1.10.2 */</code>
<code>on:</code><code>function</code><code>( types, selector, data, fn,</code><code>/*INTERNAL*/</code> <code>one ) {</code>
<code> </code><code>var</code> <code>type, origFn;</code>
<code> </code><code>// Types can be a map of types/handlers</code>
<code> </code><code>if</code> <code>(</code><code>typeof</code> <code>types ===</code><code>"object"</code> <code>) {</code>
<code> </code><code>// ( types-Object, selector, data )</code>
<code> </code><code>if</code> <code>(</code><code>typeof</code> <code>selector !==</code><code>"string"</code> <code>) {</code>
<code> </code><code>// ( types-Object, data )</code>
<code> </code><code>data = data || selector;</code>
<code> </code><code>selector = undefined;</code>
<code> </code><code>}</code>
<code> </code><code>// 周遊types對象,針對每一個屬性綁定on()方法</code>
<code> </code><code>// 将types[type]作為fn傳入</code>
<code> </code><code>for</code> <code>( type</code><code>in</code> <code>types ) {</code>
<code> </code><code>this</code><code>.on( type, selector, data, types[ type ], one );</code>
<code> </code><code>return</code> <code>this</code><code>;</code>
<code> </code><code>}</code>
<code> </code><code>// 參數修正</code>
<code> </code><code>// jQuery這種參數修正的方法很好</code>
<code> </code><code>// 可以相容多種參數形式</code>
<code> </code><code>// 可見在靈活調用的背後做了很多處理</code>
<code> </code><code>if</code> <code>( data ==</code><code>null</code> <code>&& fn ==</code><code>null</code> <code>) {</code>
<code> </code><code>// ( types, fn )</code>
<code> </code><code>fn = selector;</code>
<code> </code><code>data = selector = undefined;</code>
<code> </code><code>}</code><code>else</code> <code>if</code> <code>( fn ==</code><code>null</code> <code>) {</code>
<code> </code><code>if</code> <code>(</code><code>typeof</code> <code>selector ===</code><code>"string"</code> <code>) {</code>
<code> </code><code>// ( types, selector, fn )</code>
<code> </code><code>fn = data;</code>
<code> </code><code>data = undefined;</code>
<code> </code><code>}</code><code>else</code> <code>{</code>
<code> </code><code>// ( types, data, fn )</code>
<code> </code><code>data = selector;</code>
<code> </code><code>if</code> <code>( fn ===</code><code>false</code> <code>) {</code>
<code> </code><code>// fn傳入false時,阻止該事件的預設行為</code>
<code> </code><code>// function returnFalse() {return false;}</code>
<code> </code><code>fn = returnFalse;</code>
<code> </code><code>}</code><code>else</code> <code>if</code> <code>( !fn ) {</code>
<code> </code><code>// one()調用on()</code>
<code> </code><code>if</code> <code>( one === 1 ) {</code>
<code> </code><code>origFn = fn;</code>
<code> </code><code>fn =</code><code>function</code><code>( event ) {</code>
<code> </code><code>// Can use an empty set, since event contains the info</code>
<code> </code><code>// 用一個空jQuery對象,這樣可以使用.off方法,</code>
<code> </code><code>// 并且event帶有remove事件需要的資訊</code>
<code> </code><code>jQuery().off( event );</code>
<code> </code><code>return</code> <code>origFn.apply(</code><code>this</code><code>, arguments );</code>
<code> </code><code>};</code>
<code> </code><code>// Use same guid so caller can remove using origFn</code>
<code> </code><code>// 事件删除依賴于guid</code>
<code> </code><code>fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );</code>
<code> </code><code>// 這裡調用jQuery的each方法周遊調用on()方法的jQuery對象</code>
<code> </code><code>// 如$('li').on(...)則周遊每一個li傳入add()</code>
<code> </code><code>// 推薦使用$(document).on()或者集合元素的父元素</code>
<code> </code><code>return</code> <code>this</code><code>.each(</code><code>function</code><code>() {</code>
<code> </code><code>jQuery.event.add(</code><code>this</code><code>, types, fn, data, selector );</code>
<code> </code><code>});</code>
<code>// 事件注冊</code>
<code>add:</code><code>function</code><code>( elem, types, handler, data, selector ) {</code>
<code> </code><code>var</code> <code>tmp, events, t, handleObjIn,</code>
<code> </code><code>special, eventHandle, handleObj,</code>
<code> </code><code>handlers, type, namespaces, origType,</code>
<code> </code><code>elemData = jQuery._data( elem );</code>
<code> </code><code>// Don't attach events to noData or</code>
<code> </code><code>// text/comment nodes (but allow plain objects)</code>
<code> </code><code>// 不符合綁定條件的節點</code>
<code> </code><code>if</code> <code>( !elemData ) {</code>
<code> </code><code>return</code><code>;</code>
<code> </code><code>// Caller can pass in an object of custom data in lieu of the handler</code>
<code> </code><code>// 傳入的handler為事件對象</code>
<code> </code><code>if</code> <code>( handler.handler ) {</code>
<code> </code><code>handleObjIn = handler;</code>
<code> </code><code>handler = handleObjIn.handler;</code>
<code> </code><code>selector = handleObjIn.selector;</code>
<code> </code><code>// Make sure that the handler has a unique ID,</code>
<code> </code><code>// used to find/remove it later</code>
<code> </code><code>// 為handler配置設定一個ID,用于之後的查找或删除</code>
<code> </code><code>if</code> <code>( !handler.guid ) {</code>
<code> </code><code>handler.guid = jQuery.guid++;</code>
<code> </code><code>// Init the element's event structure and main handler,</code>
<code> </code><code>// if this is the first</code>
<code> </code><code>// 初始化events結構</code>
<code> </code><code>if</code> <code>( !(events = elemData.events) ) {</code>
<code> </code><code>events = elemData.events = {};</code>
<code> </code><code>if</code> <code>( !(eventHandle = elemData.handle) ) {</code>
<code> </code><code>eventHandle = elemData.handle =</code><code>function</code><code>( e ) {</code>
<code> </code><code>// Discard the second event of a jQuery.event.trigger() and</code>
<code> </code><code>// when an event is called after a page has unloaded</code>
<code> </code><code>return</code> <code>typeof</code> <code>jQuery !== core_strundefined &&</code>
<code> </code><code>(!e || jQuery.event.triggered !== e.type) ?</code>
<code> </code><code>jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :</code>
<code> </code><code>undefined;</code>
<code> </code><code>// Add elem as a property of the handle fn</code>
<code> </code><code>// to prevent a memory leak with IE non-native events</code>
<code> </code><code>// 添加elem為eventHandle的屬性,防止IE非本地事件的記憶體洩露?</code>
<code> </code><code>// 搜尋整個源碼,隻有110行用到了eventHandle.elem</code>
<code> </code><code>eventHandle.elem = elem;</code>
<code> </code><code>// Handle multiple events separated by a space</code>
<code> </code><code>// 處理多個以空格分隔的事件類型</code>
<code> </code><code>types = ( types ||</code><code>""</code> <code>).match( core_rnotwhite ) || [</code><code>""</code><code>];</code>
<code> </code><code>t = types.length;</code>
<code> </code><code>while</code> <code>( t-- ) {</code>
<code> </code><code>tmp = rtypenamespace.exec( types[t] ) || [];</code>
<code> </code><code>type = origType = tmp[1];</code>
<code> </code><code>// 存儲所有命名空間</code>
<code> </code><code>namespaces = ( tmp[2] ||</code><code>""</code> <code>).split(</code><code>"."</code> <code>).sort();</code>
<code> </code><code>// There *must* be a type, no attaching namespace-only handlers</code>
<code> </code><code>if</code> <code>( !type ) {</code>
<code> </code><code>continue</code><code>;</code>
<code> </code><code>// If event changes its type,</code>
<code> </code><code>// use the special event handlers for the changed type</code>
<code> </code><code>// 對于改變了事件類型的特殊事件</code>
<code> </code><code>special = jQuery.event.special[ type ] || {};</code>
<code> </code><code>// If selector defined, determine special event api type,</code>
<code> </code><code>// otherwise given type</code>
<code> </code><code>type = ( selector ? special.delegateType : special.bindType ) || type;</code>
<code> </code><code>// Update special based on newly reset type</code>
<code> </code><code>// handleObj is passed to all event handlers</code>
<code> </code><code>handleObj = jQuery.extend({</code>
<code> </code><code>type: type,</code>
<code> </code><code>origType: origType,</code>
<code> </code><code>data: data,</code>
<code> </code><code>handler: handler,</code>
<code> </code><code>guid: handler.guid,</code>
<code> </code><code>selector: selector,</code>
<code> </code><code>needsContext: selector && jQuery.expr.match.needsContext.test( selector ),</code>
<code> </code><code>namespace: namespaces.join(</code><code>"."</code><code>)</code>
<code> </code><code>}, handleObjIn );</code>
<code> </code><code>// Init the event handler queue if we're the first</code>
<code> </code><code>// 初始化handler隊列,隻初始化一次</code>
<code> </code><code>if</code> <code>( !(handlers = events[ type ]) ) {</code>
<code> </code><code>handlers = events[ type ] = [];</code>
<code> </code><code>handlers.delegateCount = 0;</code>
<code> </code><code>// Only use addEventListener/attachEvent</code>
<code> </code><code>// if the special events handler returns false</code>
<code> </code><code>if</code> <code>( !special.setup ||</code>
<code> </code><code>special.setup.call( elem, data, namespaces, eventHandle ) ===</code><code>false</code> <code>) {</code>
<code> </code><code>// Bind the global event handler to the element</code>
<code> </code><code>// 二級DOM事件/IE事件模型</code>
<code> </code><code>// eventHandle會調用jQuery.event.dispatch進行事件代理</code>
<code> </code><code>if</code> <code>( elem.addEventListener ) {</code>
<code> </code><code>elem.addEventListener( type, eventHandle,</code><code>false</code> <code>);</code>
<code> </code><code>}</code><code>else</code> <code>if</code> <code>( elem.attachEvent ) {</code>
<code> </code><code>elem.attachEvent(</code><code>"on"</code> <code>+ type, eventHandle );</code>
<code> </code><code>}</code>
<code> </code><code>}</code>
<code> </code><code>if</code> <code>( special.add ) {</code>
<code> </code><code>special.add.call( elem, handleObj );</code>
<code> </code><code>if</code> <code>( !handleObj.handler.guid ) {</code>
<code> </code><code>handleObj.handler.guid = handler.guid;</code>
<code> </code><code>// Add to the element's handler list, delegates in front</code>
<code> </code><code>if</code> <code>( selector ) {</code>
<code> </code><code>handlers.splice( handlers.delegateCount++, 0, handleObj );</code>
<code> </code><code>handlers.push( handleObj );</code>
<code> </code><code>// Keep track of which events have ever been used,</code>
<code> </code><code>// for event optimization</code>
<code> </code><code>// 跟蹤每個事件是否被使用過,為了事件優化</code>
<code> </code><code>jQuery.event.global[ type ] =</code><code>true</code><code>;</code>
<code> </code><code>// Nullify elem to prevent memory leaks in IE</code>
<code> </code><code>// 将變量置空,防止循環引用導緻IE記憶體洩露</code>
<code> </code><code>elem =</code><code>null</code><code>;</code>
<code>// 事件代理</code>
<code>dispatch:</code><code>function</code><code>( event ) {</code>
<code> </code><code>// Make a writable jQuery.Event from the native event object</code>
<code> </code><code>// jQuery定義的event對象,相容标準事件模型與IE事件模型</code>
<code> </code><code>event = jQuery.event.fix( event );</code>
<code> </code><code>var</code> <code>i, ret, handleObj, matched, j,</code>
<code> </code><code>handlerQueue = [],</code>
<code> </code><code>args = core_slice.call( arguments ),</code>
<code> </code><code>handlers = ( jQuery._data(</code><code>this</code><code>,</code><code>"events"</code> <code>) || {} )[ event.type ] || [],</code>
<code> </code><code>special = jQuery.event.special[ event.type ] || {};</code>
<code> </code><code>// Use the fix-ed jQuery.Event rather than the (read-only) native event</code>
<code> </code><code>// 使用jQuery.Event代替浏覽器的event</code>
<code> </code><code>args[0] = event;</code>
<code> </code><code>// 事件的代理節點,比如document</code>
<code> </code><code>event.delegateTarget =</code><code>this</code><code>;</code>
<code> </code><code>// Call the preDispatch hook for the mapped type,</code>
<code> </code><code>// and let it bail if desired</code>
<code> </code><code>if</code> <code>( special.preDispatch &&</code>
<code> </code><code>special.preDispatch.call(</code><code>this</code><code>, event ) ===</code><code>false</code> <code>) {</code>
<code> </code><code>// Determine handlers</code>
<code> </code><code>// 周遊事件發生節點至代理節點之間的所有節點</code>
<code> </code><code>// 比對每一個發生節點=?綁定節點</code>
<code> </code><code>handlerQueue = jQuery.event.handlers.call(</code><code>this</code><code>, event, handlers );</code>
<code> </code><code>// Run delegates first; they may want to stop propagation beneath us</code>
<code> </code><code>i = 0;</code>
<code> </code><code>// 周遊比對的節點,并且沒有被阻止冒泡</code>
<code> </code><code>while</code> <code>( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {</code>
<code> </code><code>event.currentTarget = matched.elem;</code>
<code> </code><code>j = 0;</code>
<code> </code><code>while</code> <code>( (handleObj = matched.handlers[ j++ ]) &&</code>
<code> </code><code>!event.isImmediatePropagationStopped() ) {</code>
<code> </code><code>// Triggered event must either 1) have no namespace, or</code>
<code> </code><code>// 2) have namespace(s) a subset or equal to those</code>
<code> </code><code>// in the bound event (both can have no namespace).</code>
<code> </code><code>if</code> <code>( !event.namespace_re ||</code>
<code> </code><code>event.namespace_re.test( handleObj.namespace ) ) {</code>
<code> </code><code>event.handleObj = handleObj;</code>
<code> </code><code>event.data = handleObj.data;</code>
<code> </code><code>// 傳入綁定事件的具體節點,調用事件發生函數</code>
<code> </code><code>ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle ||</code>
<code> </code><code>handleObj.handler )</code>
<code> </code><code>.apply( matched.elem, args );</code>
<code> </code><code>if</code> <code>( ret !== undefined ) {</code>
<code> </code><code>if</code> <code>( (event.result = ret) ===</code><code>false</code> <code>) {</code>
<code> </code><code>event.preventDefault();</code>
<code> </code><code>event.stopPropagation();</code>
<code> </code><code>}</code>
<code> </code><code>// Call the postDispatch hook for the mapped type</code>
<code> </code><code>if</code> <code>( special.postDispatch ) {</code>
<code> </code><code>special.postDispatch.call(</code><code>this</code><code>, event );</code>
<code> </code><code>return</code> <code>event.result;</code>