天天看點

jQuery之.on()方法

還不是完全清楚如何使用.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>

綁定過程的執行時間

jQuery之.on()方法

普通綁定相當于在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>

jQuery之.on()方法

.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>&amp;&amp; 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 &amp;&amp;</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 &amp;&amp; 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 &amp;&amp;</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++ ]) &amp;&amp; !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++ ]) &amp;&amp;</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>