【原创】jQuery1.8.2源码解析之jQuery.event
本片隨筆主要是分析了下jQuery的事件模型,即如何統一事件對象,以及處理過程。
這里簡要說明一下幾點:
jQuery通過統一的方法(第62行),eventHandle函數進行事件的分發,利用jQuery.Event統一修正了瀏覽器的event對象,注冊事件處理器時,也是注冊eventHandle,
然后統一將相關的事件信息,存儲在與dom相聯系的jQuery.cache緩存中,如下圖:
值得注意的還有事件代理和trigger的實現:
(1)事件代理是個很不錯的想法,利用了事件冒泡,在父元素上綁定事件,jQuery通過selector與事件處理器handle的selector進行匹配,從而達到代理的作用,
另外,一個元素綁定的事件處理器分為兩種:自身綁定的事件處理器 和 子元素綁定的代理事件處理器,顯然為了體現冒泡(子元素可能阻止冒泡),當該元素上的某個事件觸發時
它的代理事件處理器是要先執行,之后再執行自身的事件處理器。對于live事件而言就是事件代理,與delegate不同的是,它默認是綁定在document上了(如果沒有指定context的話)。
(2)trigger主動觸發事件,jQuery在trigger上同樣很好的實現了事件冒泡,還有元素默認操作
最后:本文主要是介紹和分析原理,對代碼主干進行源碼解析,對于一些IE兼容性的處理,沒有做過多分析,下面是源碼分析(中英文注釋):
1 var rformElems = /^(?:textarea|input|select)$/i, 2 rtypenamespace = /^([^\.]*|)(?:\.(.+)|)$/, 3 rhoverHack = /(?:^|\s)hover(\.\S+|)\b/, 4 rkeyEvent = /^key/, 5 rmouseEvent = /^(?:mouse|contextmenu)|click/, 6 rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, 7 hoverHack = function( events ) { 8 return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" ); 9 }; 10 11 /* 12 * Helper functions for managing events -- not part of the public interface. 13 * Props to Dean Edwards' addEvent library for many of the ideas. 14 */ 15 jQuery.event = { 16 17 add: function( elem, types, handler, data, selector ) { 18 19 var elemData, eventHandle, events, 20 t, tns, type, namespaces, handleObj, 21 handleObjIn, handlers, special; 22 23 // Don't attach events to noData or text/comment nodes (allow plain objects tho) 24 // 以下條件滿足一個則退出事件綁定: 25 // 是文本節點和注釋節點 26 // types為空 27 // hander為空 28 // 內部緩存數據(pvt為true) 29 if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) { 30 return; 31 } 32 33 // Caller can pass in an object of custom data in lieu of the handler 34 // 調用這可以傳遞一個自定義數據來代替handler(事件處理器) 35 // 自定義數據類似: 36 // { 37 // handler : fn 38 // ,selector : '...' 39 // } 40 if ( handler.handler ) { 41 handleObjIn = handler; 42 handler = handleObjIn.handler; 43 selector = handleObjIn.selector; 44 } 45 46 // Make sure that the handler has a unique ID, used to find/remove it later 47 // 如果改事件處理器沒有guid(沒有被添加過),那么給它添加guid,便于之后的查找和刪除 48 // jQuery.guid從1開始計算 49 if ( !handler.guid ) { 50 handler.guid = jQuery.guid++; 51 } 52 53 // Init the element's event structure and main handler, if this is the first 54 // 初始化內部數據events 55 events = elemData.events; 56 if ( !events ) { 57 elemData.events = events = {}; 58 } 59 // 初始化內部數據handle,handle將成為所有事件觸發時的處理函數 60 eventHandle = elemData.handle; 61 if ( !eventHandle ) { 62 elemData.handle = eventHandle = function( e ) { 63 // Discard the second event of a jQuery.event.trigger() and 64 // when an event is called after a page has unloaded 65 return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? 66 jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : 67 undefined; 68 }; 69 // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events 70 // 將elem作為eventHandle的屬性存儲,用來避免IE中非本地事件的內存泄漏 71 eventHandle.elem = elem; 72 } 73 74 // Handle multiple events separated by a space 75 // jQuery(...).bind("mouseover mouseout", fn); 76 // hoverHack(...)將hover事件解釋成mouseenter和mouseleave兩個事件 77 // 去除字符串前后的的空格,并拆分成事件類型數組 78 types = jQuery.trim( hoverHack(types) ).split( " " ); 79 for ( t = 0; t < types.length; t++ ) { 80 81 tns = rtypenamespace.exec( types[t] ) || []; 82 // 事件類型 83 type = tns[1]; 84 // 命名空間(值可能為undefined) 85 // 多級命名空間(其實沒所謂的多級),排序 86 namespaces = ( tns[2] || "" ).split( "." ).sort(); 87 88 // If event changes its type, use the special event handlers for the changed type 89 // 特殊的事件需要進行特殊處理,比如focus,blur等 90 special = jQuery.event.special[ type ] || {}; 91 92 // If selector defined, determine special event api type, otherwise given type 93 // 如果selector有值,則表示適用代理 94 type = ( selector ? special.delegateType : special.bindType ) || type; 95 96 // Update special based on newly reset type 97 special = jQuery.event.special[ type ] || {}; 98 99 // handleObj is passed to all event handlers 100 // 每一個事件處理器都對應存儲著一個handleObj 101 // type與oriType肯能不同,比如oriType為mouseenter,type為mouseover 102 // 這里extend了handleObjIn,這意味著使用者可以在傳遞進來的handle里大做文章,存儲自定義的數據。 103 handleObj = jQuery.extend({ 104 type: type, // fix后的事件類型 105 origType: tns[1], // 原始的事件類型 106 data: data, // 傳遞進來的數據 107 handler: handler, // 事件處理器 108 guid: handler.guid, // 事件處理器的唯一id 109 selector: selector, // 代理事件處理器需要的選擇器,用來過濾 110 namespace: namespaces.join(".") //命名空間(經過排序) 111 }, handleObjIn ); 112 113 // Init the event handler queue if we're the first 114 // 獲取type事件下的所有handler(array) 115 handlers = events[ type ]; 116 // 如果為空,進行第一次初始化 117 if ( !handlers ) { 118 handlers = events[ type ] = []; 119 // handlers中作為代理的個數 120 handlers.delegateCount = 0; 121 122 // Only use addEventListener/attachEvent if the special events handler returns false 123 // 特殊事件需要用setup進行特殊處理,如:focusin,focusout等(后面有處理) 124 if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { 125 // Bind the global event handler to the element 126 // 將事件統一綁定到eventHandle,統一接口。 127 // 采用冒泡,與ie兼容 128 if ( elem.addEventListener ) { 129 elem.addEventListener( type, eventHandle, false ); 130 131 } else if ( elem.attachEvent ) { 132 elem.attachEvent( "on" + type, eventHandle ); 133 } 134 } 135 } 136 137 if ( special.add ) { 138 special.add.call( elem, handleObj ); 139 140 if ( !handleObj.handler.guid ) { 141 handleObj.handler.guid = handler.guid; 142 } 143 } 144 145 // Add to the element's handler list, delegates in front 146 if ( selector ) { 147 // 將事件代理處理器添加到handlers的最前面,方便之后代理處理器優先執行,用的很巧妙 148 handlers.splice( handlers.delegateCount++, 0, handleObj ); 149 } else { 150 // 普通事件處理器添加到依次push到handler list中 151 handlers.push( handleObj ); 152 } 153 154 // Keep track of which events have ever been used, for event optimization 155 // 記錄哪些事件曾將使用過,為了事件的優化 156 jQuery.event.global[ type ] = true; 157 } 158 159 // Nullify elem to prevent memory leaks in IE 160 // 去除elem的引用,防止ie內存泄露 161 elem = null; 162 }, 163 164 global: {}, 165 166 // Detach an event or set of events from an element 167 remove: function( elem, types, handler, selector, mappedTypes ) { 168 169 var t, tns, type, origType, namespaces, origCount, 170 j, events, special, eventType, handleObj, 171 elemData = jQuery.hasData( elem ) && jQuery._data( elem ); 172 173 if ( !elemData || !(events = elemData.events) ) { 174 return; 175 } 176 177 // Once for each type.namespace in types; type may be omitted 178 types = jQuery.trim( hoverHack( types || "" ) ).split(" "); 179 for ( t = 0; t < types.length; t++ ) { 180 tns = rtypenamespace.exec( types[t] ) || []; 181 type = origType = tns[1]; 182 namespaces = tns[2]; 183 184 // Unbind all events (on this namespace, if provided) for the element 185 // 如果types為'.mynamespace.hello'時,type此時就為空,那么刪除該命名空間下的所有事件 186 if ( !type ) { 187 for ( type in events ) { 188 jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); 189 } 190 continue; 191 } 192 193 special = jQuery.event.special[ type ] || {}; 194 // 修正type 195 type = ( selector? special.delegateType : special.bindType ) || type; 196 // 該type(事件類型)下的所有函數處理器handleObj(array) 197 eventType = events[ type ] || []; 198 origCount = eventType.length; 199 // 動態生成正則表達式,匹配命名空間 200 namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.|)") + "(\\.|$)") : null; 201 202 // Remove matching events 203 // 遍歷handleObj list查找匹配的handler,并執行刪除操作 204 // mappedTypes 暫時不知道何用,應該是內部控制吧 205 for ( j = 0; j < eventType.length; j++ ) { 206 handleObj = eventType[ j ]; 207 208 // 一系列的邏輯,表示的基本意思如下: 209 // 1. origType === handleObj.origTypes是為了處理jQuery會將mouseenter修正為mouseover這種情況,因為此時mouseenter和mouseover的handleObjs 210 // 會處于同一個集合(對象)里,而它的區別就在于handleObj.oriType,這里做判斷,保證能夠正確刪除mouseenter或者mouseover事件下的事件處理器 211 // 2. 如果handler不存在,那么就當作是刪除該事件下的所有函數處理器,如果存在,則要handler.guid === handleObj.guid保證刪除指定事件 212 // 處理器,因為handler和handleObj是通過guid對應的(一對多的關系,即一個事件處理器可以被同一個事件注冊和調用多次)。 213 // 3. 如果命名空間不存在,那么就忽略命名空間,否則需要進行匹配,這里如果命名空間為多級,只需要其中的一級或多級組合便可以指定其,并刪除。 214 // 如:如果注冊click事件,命名空間是'aa.bb.cc',處理器為fn1,那么'click.aa','click.aa.cc'都是可以用來指定并刪除fn1的(所以說是多級是不合理的)。 215 // 4. 如果selector不存在,那么忽略selector,否則如果存在則尋找selector === handleObj.selector的事件處理器進行刪除,存在一個特殊值'**', 216 // 如果selector為'**',那么刪除所有存在selector的handleObj 217 if ( ( mappedTypes || origType === handleObj.origType ) && 218 ( !handler || handler.guid === handleObj.guid ) && 219 ( !namespaces || namespaces.test( handleObj.namespace ) ) && 220 ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { 221 //這里的設計比較好,保整handleObj的正常刪除 222 eventType.splice( j--, 1 ); 223 224 if ( handleObj.selector ) { 225 // 事件代理處理器個數減減 226 eventType.delegateCount--; 227 } 228 if ( special.remove ) { 229 special.remove.call( elem, handleObj ); 230 } 231 } 232 } 233 234 // Remove generic event handler if we removed something and no more handlers exist 235 // (avoids potential for endless recursion during removal of special event handlers) 236 // eventType如果從有到無,那么進行一系列的清除工作,special事件仍然做自己的特殊處理 237 if ( eventType.length === 0 && origCount !== eventType.length ) { 238 if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { 239 jQuery.removeEvent( elem, type, elemData.handle ); 240 } 241 // 刪除緩存中events[type] 242 delete events[ type ]; 243 } 244 } 245 246 // Remove the expando if it's no longer used 247 // 如果不存在任何事件處理器,則去除elemData.handle(所有事件的統一事件處理器) 248 if ( jQuery.isEmptyObject( events ) ) { 249 delete elemData.handle; 250 251 // removeData also checks for emptiness and clears the expando if empty 252 // so use it instead of delete 253 // 用jQuery.removeData刪除events,是為了做好清理工作(包括dom上的expando屬性或者普通js對象的expando對象,以及緩存在jQuery.cahe的數據) 254 jQuery.removeData( elem, "events", true ); 255 } 256 }, 257 258 // Events that are safe to short-circuit if no handlers are attached. 259 // Native DOM events should not be added, they may have inline handlers. 260 customEvent: { 261 "getData": true, 262 "setData": true, 263 "changeData": true 264 }, 265 266 // onlyHandlers在調用triggerHandler時使用 267 trigger: function( event, data, elem, onlyHandlers ) { 268 // Don't do events on text and comment nodes 269 // 不處理文本節點和注釋節點 270 if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) { 271 return; 272 } 273 274 // Event object or event type 275 var cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType, 276 277 // 兼容jQuery.Event(object) 和 event(string) 278 type = event.type || event, 279 namespaces = []; 280 281 // focus/blur morphs to focusin/out; ensure we're not firing them right now 282 if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { 283 return; 284 } 285 286 if ( type.indexOf( "!" ) >= 0 ) { 287 // Exclusive events trigger only for the exact event (no namespaces) 288 type = type.slice(0, -1); 289 exclusive = true; 290 } 291 // 解析命名空間 292 if ( type.indexOf( "." ) >= 0 ) { 293 // Namespaced trigger; create a regexp to match event type in handle() 294 namespaces = type.split("."); 295 type = namespaces.shift(); 296 namespaces.sort(); 297 } 298 299 if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) { 300 // No jQuery handlers for this event type, and it can't have inline handlers 301 return; 302 } 303 304 // Caller can pass in an Event, Object, or just an event type string 305 event = typeof event === "object" ? 306 // jQuery.Event object 307 // jQuery.Event或者修正過的event對象 308 event[ jQuery.expando ] ? event : 309 // Object literal 310 // 字面對象,如{type:'click',...} 311 new jQuery.Event( type, event ) : 312 // Just the event type (string) 313 // event(string),如:'click',則創建jQuery.Event對象 314 new jQuery.Event( type ); 315 316 event.type = type; 317 event.isTrigger = true; 318 event.exclusive = exclusive; 319 event.namespace = namespaces.join( "." ); 320 event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)") : null; 321 ontype = type.indexOf( ":" ) < 0 ? "on" + type : ""; 322 323 // Handle a global trigger 324 if ( !elem ) { 325 326 // TODO: Stop taunting the data cache; remove global events and always attach to document 327 cache = jQuery.cache; 328 for ( i in cache ) { 329 if ( cache[ i ].events && cache[ i ].events[ type ] ) { 330 jQuery.event.trigger( event, data, cache[ i ].handle.elem, true ); 331 } 332 } 333 return; 334 } 335 336 // Clean up the event in case it is being reused 337 // 清理event,防止該event正在使用中,有殘留歷史 338 event.result = undefined; 339 if ( !event.target ) { 340 event.target = elem; 341 } 342 343 // Clone any incoming data and prepend the event, creating the handler arg list 344 // 將data轉換為數組 345 data = data != null ? jQuery.makeArray( data ) : []; 346 // 將事件對象插入數組最前面(開始位置),最好將這個數組作為參數傳遞給事件處理函數 347 data.unshift( event ); 348 349 // Allow special events to draw outside the lines 350 special = jQuery.event.special[ type ] || {}; 351 if ( special.trigger && special.trigger.apply( elem, data ) === false ) { 352 return; 353 } 354 355 // Determine event propagation path in advance, per W3C events spec (#9951) 356 // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) 357 // 用eventPath存儲冒泡路徑[[elem, 'click'], [elemParent, 'click'],...,[window,'click']] 358 eventPath = [[ elem, special.bindType || type ]]; 359 if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { 360 361 bubbleType = special.delegateType || type; 362 cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode; 363 for ( old = elem; cur; cur = cur.parentNode ) { 364 eventPath.push([ cur, bubbleType ]); 365 old = cur; 366 } 367 368 // Only add window if we got to document (e.g., not plain obj or detached DOM) 369 // 冒泡是一直冒泡到window對象 370 if ( old === (elem.ownerDocument || document) ) { 371 eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]); 372 } 373 } 374 375 // Fire handlers on the event path 376 // 依次冒泡調用指定type的事件吃利器 377 for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) { 378 379 cur = eventPath[i][0]; 380 event.type = eventPath[i][1]; 381 // 查詢每個元素type類型的事件處理器 382 handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); 383 if ( handle ) { 384 // 這里其實很重要,在進行冒泡的時候,傳遞的是同一個event對象(在data中) 385 // 也就是在這里便可以通過event對象或者return false,隨時停止冒泡 386 handle.apply( cur, data ); 387 } 388 // Note that this is a bare JS function and not a jQuery handler 389 // 處理通過onType屬性添加的事件處理器(如:elem.onClick = function(){...};) 390 // 這里就可以知道trigger的時候onType事件是在通過jquery添加的事件后面執行的 391 handle = ontype && cur[ ontype ]; 392 if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) { 393 // 注意這里onType事件處理器的return false只有可能阻止默認行為 394 event.preventDefault(); 395 } 396 } 397 // 修正type,防止事件處理器改變event.type的值 398 event.type = type; 399 400 // If nobody prevented the default action, do it now 401 // 這里主要是用來執行元素的默認操作 402 if ( !onlyHandlers && !event.isDefaultPrevented() ) { 403 404 if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && 405 !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { 406 407 // Call a native DOM method on the target with the same name name as the event. 408 // Can't use an .isFunction() check here because IE6/7 fails that test. 409 // Don't do default actions on window, that's where global variables be (#6170) 410 // IE<9 dies on focus/blur to hidden element (#1486) 411 if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) { 412 413 // Don't re-trigger an onFOO event when we call its FOO() method 414 // 假設type為click 415 // 因為下面想通過click()來觸發默認操作,但是又不想執行對應的事件處理器(re-trigger), 416 // 所以需要做兩方面工作: 417 // 首先將elem.onclick = null; 418 // 然后將jQuery.event.triggered = 'click'; 將在入口handle(第62行)不再dispatch了 419 // 之后再將它們還原 420 old = elem[ ontype ]; 421 422 if ( old ) { 423 // 暫時去除事件處理器 424 elem[ ontype ] = null; 425 } 426 427 // Prevent re-triggering of the same event, since we already bubbled it above 428 // 相當于是標記,表示事件處理器已經調用過了 429 jQuery.event.triggered = type; 430 // 再次調用,如elem.click(),(即trigge click)再次冒泡,不過這次執行入口handle,不會執行dispatch之后的代碼了,只為觸發默認操作 431 elem[ type ](); 432 jQuery.event.triggered = undefined; 433 434 if ( old ) { 435 elem[ ontype ] = old; 436 } 437 } 438 } 439 } 440 441 return event.result; 442 }, 443 444 dispatch: function( event ) { 445 446 // Make a writable jQuery.Event from the native event object 447 // event || window.event 兼容標準事件模型和IE事件模型 448 // fix 修正event,返回jQuery.Event對象 449 event = jQuery.event.fix( event || window.event ); 450 451 var i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related, 452 // handle數組 453 handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []), 454 // handle中代理的個數 455 delegateCount = handlers.delegateCount, 456 // 瀏覽器傳來的參數,偽數組轉換為真正的數組 457 // args[0]為原生的event對象 458 args = [].slice.call( arguments ), 459 run_all = !event.exclusive && !event.namespace, 460 special = jQuery.event.special[ event.type ] || {}, 461 handlerQueue = []; 462 463 // Use the fix-ed jQuery.Event rather than the (read-only) native event 464 // 修正args[0]。 465 // 因為在IE下,傳入的參數event為undefined,就意味著arguments.length為0,改變event并不會影響arguments,所以arguments仍然為undefined 466 args[0] = event; 467 // 事件代理元素 468 event.delegateTarget = this; 469 470 // Call the preDispatch hook for the mapped type, and let it bail if desired 471 if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { 472 return; 473 } 474 475 // Determine handlers that should run if there are delegated events 476 // 對于代理處理器,需要進行一定過濾,決定哪些代理處理器可以運行 477 // Avoid non-left-click bubbling in Firefox (#3861) 478 // 火狐瀏覽器右鍵或者中鍵點擊時,會錯誤地冒泡到document的click事件,并且stopPropagation也無效 479 // 故這樣的代理處理器需要避免 480 // 如果存在代理處理器(若是click事件,要求是左鍵觸發的),那么才進行事件代理 481 if ( delegateCount && !(event.button && event.type === "click") ) { 482 483 // Pregenerate a single jQuery object for reuse with .is() 484 // 生成一個jQuery對象,這里的this沒有任何用處,后面會將其dom元素覆蓋,只為之后重復調用is()方法, 485 // 而不必生成新的jQuery對象 486 jqcur = jQuery(this); 487 jqcur.context = this; 488 489 // 從觸發事件的元素開始,一直向根節點搜索匹配selector的元素,并執行事件處理函數 490 for ( cur = event.target; cur != this; cur = cur.parentNode || this ) { 491 492 // Don't process clicks (ONLY) on disabled elements (#6911, #8165, #xxxx) 493 // 不處理元素為disabled的click事件 494 if ( cur.disabled !== true || event.type !== "click" ) { 495 // 匹配的selector的緩存,避免每次調用is()方法進行判斷 496 selMatch = {}; 497 //暫存匹配selector的事件處理器 498 matches = []; 499 // 將其轉換為jQuery對象,后面便可以調用is()方法 500 jqcur[0] = cur; 501 // 遍歷代理handlers,查找匹配它們的selector 502 for ( i = 0; i < delegateCount; i++ ) { 503 handleObj = handlers[ i ]; 504 sel = handleObj.selector; 505 506 if ( selMatch[ sel ] === undefined ) { 507 // 調用is方法,看是否匹配當前元素 508 selMatch[ sel ] = jqcur.is( sel ); 509 } 510 // 如果匹配,那么放入matches里暫存 511 if ( selMatch[ sel ] ) { 512 matches.push( handleObj ); 513 } 514 } 515 if ( matches.length ) { 516 // 將dom元素和對應的代理事件處理器以對象的形式統一添加到handlerQueue(與后面的普通事件處理器是一樣的) 517 handlerQueue.push({ elem: cur, matches: matches }); 518 } 519 } 520 } 521 } 522 523 // Add the remaining (directly-bound) handlers 524 // 將dom元素和對應的普通的事件處理器(非代理)添加到處理隊列(handlerQueue)中去 525 if ( handlers.length > delegateCount ) { 526 handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) }); 527 } 528 529 // Run delegates first; they may want to stop propagation beneath us 530 // 優先執行代理處理器,因為有可能被代理的元素作為子元素會阻止傳播 531 for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) { 532 matched = handlerQueue[ i ]; 533 // 正在觸發事件回調的元素 534 event.currentTarget = matched.elem; 535 // 依次執行改元素對應的事件回調(在沒有立刻阻止傳播的情況下) 536 for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) { 537 handleObj = matched.matches[ j ]; 538 539 // Triggered event must either 1) be non-exclusive and have no namespace, or 540 // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). 541 if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) { 542 // 注冊事件處理器時傳進來的數據 543 event.data = handleObj.data; 544 // 事件對應的事件處理器對象 545 event.handleObj = handleObj; 546 // 調用事件回調,特殊事件特殊處理 547 ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) 548 .apply( matched.elem, args ); 549 //如果返回值不是undefined,那么賦值到event.result中 550 if ( ret !== undefined ) { 551 event.result = ret; 552 //如果返回值是false,那么阻止冒泡傳播和元素的默認操作 553 if ( ret === false ) { 554 event.preventDefault(); 555 event.stopPropagation(); 556 } 557 } 558 } 559 } 560 } 561 562 // Call the postDispatch hook for the mapped type 563 if ( special.postDispatch ) { 564 special.postDispatch.call( this, event ); 565 } 566 // 返回最后一次事件回調的值 567 return event.result; 568 }, 569 570 // Includes some event props shared by KeyEvent and MouseEvent 571 // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 *** 572 props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), 573 574 // 所謂hook就是攔截修改的意思 575 fixHooks: {}, 576 577 keyHooks: { 578 props: "char charCode key keyCode".split(" "), 579 filter: function( event, original ) { 580 581 // Add which for key events 582 if ( event.which == null ) { 583 event.which = original.charCode != null ? original.charCode : original.keyCode; 584 } 585 586 return event; 587 } 588 }, 589 590 mouseHooks: { 591 props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), 592 filter: function( event, original ) { 593 var eventDoc, doc, body, 594 button = original.button, 595 fromElement = original.fromElement; 596 597 // Calculate pageX/Y if missing and clientX/Y available 598 if ( event.pageX == null && original.clientX != null ) { 599 eventDoc = event.target.ownerDocument || document; 600 doc = eventDoc.documentElement; 601 body = eventDoc.body; 602 603 event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); 604 event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); 605 } 606 607 // Add relatedTarget, if necessary 608 if ( !event.relatedTarget && fromElement ) { 609 event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; 610 } 611 612 // Add which for click: 1 === left; 2 === middle; 3 === right 613 // Note: button is not normalized, so don't use it 614 if ( !event.which && button !== undefined ) { 615 event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); 616 } 617 618 return event; 619 } 620 }, 621 622 // 修正event,利用統一的jQuery.Event兼容各個瀏覽器 623 fix: function( event ) { 624 // 如果已經fixed過,表明是jQuery.Event,則直接返回 625 if ( event[ jQuery.expando ] ) { 626 return event; 627 } 628 629 // Create a writable copy of the event object and normalize some properties 630 var i, prop, 631 originalEvent = event, 632 fixHook = jQuery.event.fixHooks[ event.type ] || {}, 633 // 如果是key或者mouse事件,添加額外的屬性(props) 634 copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; 635 636 event = jQuery.Event( originalEvent ); 637 638 // 將瀏覽器原生event的屬性賦值到新創建的jQuery.Event對象中去 639 for ( i = copy.length; i; ) { 640 prop = copy[ --i ]; 641 event[ prop ] = originalEvent[ prop ]; 642 } 643 644 // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2) 645 // 兼容觸發事件的文檔元素 646 // Safari2下event.target可能為undefined 647 if ( !event.target ) { 648 event.target = originalEvent.srcElement || document; 649 } 650 651 // Target should not be a text node (#504, Safari) 652 // 如果是文本節點,則取其parent 653 if ( event.target.nodeType === 3 ) { 654 event.target = event.target.parentNode; 655 } 656 657 // For mouse/key events, metaKey==false if it's undefined (#3368, #11328; IE6/7/8) 658 event.metaKey = !!event.metaKey; 659 660 return fixHook.filter? fixHook.filter( event, originalEvent ) : event; 661 }, 662 663 special: { 664 load: { 665 // Prevent triggered image.load events from bubbling to window.load 666 noBubble: true 667 }, 668 669 focus: { 670 delegateType: "focusin" 671 }, 672 blur: { 673 delegateType: "focusout" 674 }, 675 676 beforeunload: { 677 setup: function( data, namespaces, eventHandle ) { 678 // We only want to do this special case on windows 679 if ( jQuery.isWindow( this ) ) { 680 this.onbeforeunload = eventHandle; 681 } 682 }, 683 684 teardown: function( namespaces, eventHandle ) { 685 if ( this.onbeforeunload === eventHandle ) { 686 this.onbeforeunload = null; 687 } 688 } 689 } 690 }, 691 692 simulate: function( type, elem, event, bubble ) { 693 // Piggyback on a donor event to simulate a different one. 694 // Fake originalEvent to avoid donor's stopPropagation, but if the 695 // simulated event prevents default then we do the same on the donor. 696 var e = jQuery.extend( 697 new jQuery.Event(), 698 event, 699 { type: type, 700 isSimulated: true, 701 originalEvent: {} 702 } 703 ); 704 if ( bubble ) { 705 jQuery.event.trigger( e, null, elem ); 706 } else { 707 jQuery.event.dispatch.call( elem, e ); 708 } 709 if ( e.isDefaultPrevented() ) { 710 event.preventDefault(); 711 } 712 } 713 }; 714 715 // Some plugins are using, but it's undocumented/deprecated and will be removed. 716 // The 1.7 special event interface should provide all the hooks needed now. 717 jQuery.event.handle = jQuery.event.dispatch; 718 719 // 調用原生的瀏覽器方法注銷事件處理器,做兼容行處理 720 jQuery.removeEvent = document.removeEventListener ? 721 function( elem, type, handle ) { 722 if ( elem.removeEventListener ) { 723 elem.removeEventListener( type, handle, false ); 724 } 725 } : 726 function( elem, type, handle ) { 727 var name = "on" + type; 728 729 if ( elem.detachEvent ) { 730 731 // #8545, #7054, preventing memory leaks for custom events in IE6-8 – 732 // detachEvent needed property on element, by name of that event, to properly expose it to GC 733 if ( typeof elem[ name ] === "undefined" ) { 734 elem[ name ] = null; 735 } 736 737 elem.detachEvent( name, handle ); 738 } 739 }; 740 741 // jQuery為統一event對象而封裝的Event類 742 jQuery.Event = function( src, props ) { 743 // Allow instantiation without the 'new' keyword 744 // 兼容jQuery.Event()實例化Event對象 745 if ( !(this instanceof jQuery.Event) ) { 746 return new jQuery.Event( src, props ); 747 } 748 749 // jQuery.Event object 或者 瀏覽器的Event object 750 if ( src && src.type ) { 751 // 存儲原先的Event對象 752 this.originalEvent = src; 753 this.type = src.type; 754 755 // Events bubbling up the document may have been marked as prevented 756 // by a handler lower down the tree; reflect the correct value. 757 this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || 758 src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; 759 760 // event type 761 // event字面量,如:'click' 762 } else { 763 this.type = src; 764 } 765 766 // Put explicitly provided properties onto the event object 767 // 提供的props將被extend進創建的event中作為屬性 768 if ( props ) { 769 jQuery.extend( this, props ); 770 } 771 772 // Create a timestamp if incoming event doesn't have one 773 this.timeStamp = src && src.timeStamp || jQuery.now(); 774 775 // Mark it as fixed 776 // 標記已經fiexed了 777 this[ jQuery.expando ] = true; 778 }; 779 780 // 函數的形式,避免直接修改屬性值 781 function returnFalse() { 782 return false; 783 } 784 function returnTrue() { 785 return true; 786 } 787 788 // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding 789 // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html 790 // jQuery.Event對象的公用方法 791 jQuery.Event.prototype = { 792 // 阻止元素默認操作,做兼容性處理 793 preventDefault: function() { 794 this.isDefaultPrevented = returnTrue; 795 796 var e = this.originalEvent; 797 if ( !e ) { 798 return; 799 } 800 801 // if preventDefault exists run it on the original event 802 if ( e.preventDefault ) { 803 e.preventDefault(); 804 805 // otherwise set the returnValue property of the original event to false (IE) 806 } else { 807 e.returnValue = false; 808 } 809 }, 810 // 阻止元素冒泡傳播,做兼容性處理 811 stopPropagation: function() { 812 this.isPropagationStopped = returnTrue; 813 814 var e = this.originalEvent; 815 if ( !e ) { 816 return; 817 } 818 // if stopPropagation exists run it on the original event 819 if ( e.stopPropagation ) { 820 e.stopPropagation(); 821 } 822 // otherwise set the cancelBubble property of the original event to true (IE) 823 e.cancelBubble = true; 824 }, 825 // 立刻阻止傳播,指的是立即停止與本元素相關的之后的所有事件處理器以及其他元素的事件傳播 826 stopImmediatePropagation: function() { 827 this.isImmediatePropagationStopped = returnTrue; 828 this.stopPropagation(); 829 }, 830 isDefaultPrevented: returnFalse, 831 isPropagationStopped: returnFalse, 832 isImmediatePropagationStopped: returnFalse 833 }; 834 835 // Create mouseenter/leave events using mouseover/out and event-time checks 836 jQuery.each({ 837 mouseenter: "mouseover", 838 mouseleave: "mouseout" 839 }, function( orig, fix ) { 840 jQuery.event.special[ orig ] = { 841 delegateType: fix, 842 bindType: fix, 843 844 handle: function( event ) { 845 var ret, 846 target = this, 847 related = event.relatedTarget, 848 handleObj = event.handleObj, 849 selector = handleObj.selector; 850 851 // For mousenter/leave call the handler if related is outside the target. 852 // NB: No relatedTarget if the mouse left/entered the browser window 853 if ( !related || (related !== target && !jQuery.contains( target, related )) ) { 854 event.type = handleObj.origType; 855 ret = handleObj.handler.apply( this, arguments ); 856 event.type = fix; 857 } 858 return ret; 859 } 860 }; 861 }); 862 863 // IE submit delegation 864 if ( !jQuery.support.submitBubbles ) { 865 866 jQuery.event.special.submit = { 867 setup: function() { 868 // Only need this for delegated form submit events 869 if ( jQuery.nodeName( this, "form" ) ) { 870 return false; 871 } 872 873 // Lazy-add a submit handler when a descendant form may potentially be submitted 874 jQuery.event.add( this, "click._submit keypress._submit", function( e ) { 875 // Node name check avoids a VML-related crash in IE (#9807) 876 var elem = e.target, 877 form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; 878 if ( form && !jQuery._data( form, "_submit_attached" ) ) { 879 jQuery.event.add( form, "submit._submit", function( event ) { 880 event._submit_bubble = true; 881 }); 882 jQuery._data( form, "_submit_attached", true ); 883 } 884 }); 885 // return undefined since we don't need an event listener 886 }, 887 888 postDispatch: function( event ) { 889 // If form was submitted by the user, bubble the event up the tree 890 if ( event._submit_bubble ) { 891 delete event._submit_bubble; 892 if ( this.parentNode && !event.isTrigger ) { 893 jQuery.event.simulate( "submit", this.parentNode, event, true ); 894 } 895 } 896 }, 897 898 teardown: function() { 899 // Only need this for delegated form submit events 900 if ( jQuery.nodeName( this, "form" ) ) { 901 return false; 902 } 903 904 // Remove delegated handlers; cleanData eventually reaps submit handlers attached above 905 jQuery.event.remove( this, "._submit" ); 906 } 907 }; 908 } 909 910 // IE change delegation and checkbox/radio fix 911 if ( !jQuery.support.changeBubbles ) { 912 913 jQuery.event.special.change = { 914 915 setup: function() { 916 917 if ( rformElems.test( this.nodeName ) ) { 918 // IE doesn't fire change on a check/radio until blur; trigger it on click 919 // after a propertychange. Eat the blur-change in special.change.handle. 920 // This still fires onchange a second time for check/radio after blur. 921 if ( this.type === "checkbox" || this.type === "radio" ) { 922 jQuery.event.add( this, "propertychange._change", function( event ) { 923 if ( event.originalEvent.propertyName === "checked" ) { 924 this._just_changed = true; 925 } 926 }); 927 jQuery.event.add( this, "click._change", function( event ) { 928 if ( this._just_changed && !event.isTrigger ) { 929 this._just_changed = false; 930 } 931 // Allow triggered, simulated change events (#11500) 932 jQuery.event.simulate( "change", this, event, true ); 933 }); 934 } 935 return false; 936 } 937 // Delegated event; lazy-add a change handler on descendant inputs 938 jQuery.event.add( this, "beforeactivate._change", function( e ) { 939 var elem = e.target; 940 941 if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "_change_attached" ) ) { 942 jQuery.event.add( elem, "change._change", function( event ) { 943 if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { 944 jQuery.event.simulate( "change", this.parentNode, event, true ); 945 } 946 }); 947 jQuery._data( elem, "_change_attached", true ); 948 } 949 }); 950 }, 951 952 handle: function( event ) { 953 var elem = event.target; 954 955 // Swallow native change events from checkbox/radio, we already triggered them above 956 if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { 957 return event.handleObj.handler.apply( this, arguments ); 958 } 959 }, 960 961 teardown: function() { 962 jQuery.event.remove( this, "._change" ); 963 964 return !rformElems.test( this.nodeName ); 965 } 966 }; 967 } 968 969 // Create "bubbling" focus and blur events 970 if ( !jQuery.support.focusinBubbles ) { 971 jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { 972 973 // Attach a single capturing handler while someone wants focusin/focusout 974 var attaches = 0, 975 handler = function( event ) { 976 jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); 977 }; 978 979 jQuery.event.special[ fix ] = { 980 setup: function() { 981 if ( attaches++ === 0 ) { 982 document.addEventListener( orig, handler, true ); 983 } 984 }, 985 teardown: function() { 986 if ( --attaches === 0 ) { 987 document.removeEventListener( orig, handler, true ); 988 } 989 } 990 }; 991 }); 992 } 993 994 jQuery.fn.extend({ 995 // 注冊事件處理器 996 on: function( types, selector, data, fn, /*INTERNAL*/ one ) { 997 var origFn, type; 998 999 // Types can be a map of types/handlers 1000 // types可以為對象,是類型/函數對集合,如: 1001 // { 1002 // 'click' : fn1 1003 // ,'mouseover' : fn2 1004 // , ... 1005 // } 1006 if ( typeof types === "object" ) { 1007 // ( types-Object, selector, data ) 1008 if ( typeof selector !== "string" ) { // && selector != null 1009 // ( types-Object, data ) 1010 data = data || selector; 1011 selector = undefined; 1012 } 1013 for ( type in types ) { 1014 this.on( type, selector, data, types[ type ], one ); 1015 } 1016 return this; 1017 } 1018 1019 // 以下是根據某些參數有無,對data和selector采取一定的判定匹配措施 1020 if ( data == null && fn == null ) { 1021 // ( types, fn ) 1022 fn = selector; 1023 data = selector = undefined; 1024 } else if ( fn == null ) { 1025 if ( typeof selector === "string" ) { 1026 // ( types, selector, fn ) 1027 fn = data; 1028 data = undefined; 1029 } else { 1030 // ( types, data, fn ) 1031 fn = data; 1032 data = selector; 1033 selector = undefined; 1034 } 1035 } 1036 // 當fn賦值為false時,用返回值為false的函數替換,相當于是一個快捷鍵 1037 if ( fn === false ) { 1038 fn = returnFalse; 1039 // 如果fn為空,則退出,什么都不做 1040 } else if ( !fn ) { 1041 return this; 1042 } 1043 // one參數是內部調用的,one為1表示事件只在內部被調用一次 1044 if ( one === 1 ) { 1045 origFn = fn; 1046 fn = function( event ) { 1047 // Can use an empty set, since event contains the info 1048 // 創建jQuery空對象,調用off方法,傳入帶有信息的event參數(其中包括注冊時的所有信息),在off中會對 1049 // event對象做特殊處理,從而刪除指定type的handler,保證只調用一次 1050 jQuery().off( event ); 1051 return origFn.apply( this, arguments ); 1052 }; 1053 // Use same guid so caller can remove using origFn 1054 fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); 1055 } 1056 return this.each( function() { 1057 jQuery.event.add( this, types, fn, data, selector ); 1058 }); 1059 }, 1060 // 注冊只能被觸發一次的事件處理器 1061 one: function( types, selector, data, fn ) { 1062 return this.on( types, selector, data, fn, 1 ); 1063 }, 1064 off: function( types, selector, fn ) { 1065 var handleObj, type; 1066 // 如果傳進來的是event對象,那么進行如下處理(因為event對象包含off事件處理器需要的全部信息) 1067 if ( types && types.preventDefault && types.handleObj ) { 1068 // ( event ) dispatched jQuery.Event 1069 handleObj = types.handleObj; 1070 jQuery( types.delegateTarget ).off( 1071 handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, 1072 handleObj.selector, 1073 handleObj.handler 1074 ); 1075 return this; 1076 } 1077 //types可以為對象,是類型/函數對集合,同上面的on 1078 if ( typeof types === "object" ) { 1079 // ( types-object [, selector] ) 1080 for ( type in types ) { 1081 this.off( type, selector, types[ type ] ); 1082 } 1083 return this; 1084 } 1085 // 只有兩個參數的情況 1086 if ( selector === false || typeof selector === "function" ) { 1087 // ( types [, fn] ) 1088 fn = selector; 1089 selector = undefined; 1090 } 1091 // 當fn賦值為false時,用返回值為false的函數替換,相當于是一個快捷鍵 1092 if ( fn === false ) { 1093 fn = returnFalse; 1094 } 1095 return this.each(function() { 1096 jQuery.event.remove( this, types, fn, selector ); 1097 }); 1098 }, 1099 // 綁定事件,與on的區別在于不提供selector,這意味著它不支持事件代理 1100 bind: function( types, data, fn ) { 1101 return this.on( types, null, data, fn ); 1102 }, 1103 unbind: function( types, fn ) { 1104 return this.off( types, null, fn ); 1105 }, 1106 // 與bind類似,但是它支持后綁定 1107 live: function( types, data, fn ) { 1108 // 默認不指定上下問的情況下,代理元素是document,所以說live其實是用的事件代理實現的 1109 jQuery( this.context ).on( types, this.selector, data, fn ); 1110 return this; 1111 }, 1112 die: function( types, fn ) { 1113 jQuery( this.context ).off( types, this.selector || "**", fn ); 1114 return this; 1115 }, 1116 // 事件代理接口,根本上就是調用on 1117 delegate: function( selector, types, data, fn ) { 1118 return this.on( types, selector, data, fn ); 1119 }, 1120 undelegate: function( selector, types, fn ) { 1121 // ( namespace ) or ( selector, types [, fn] ) 1122 return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector || "**", fn ); 1123 }, 1124 // 觸發指定類型的事件回調函數列表 1125 trigger: function( type, data ) { 1126 return this.each(function() { 1127 jQuery.event.trigger( type, data, this ); 1128 }); 1129 }, 1130 // 與trigger幾乎相同,不同的是,它只對jQuery集合中的第一個元素trigger,而且不冒泡,不執行默認操作 1131 // 其內部調用trigger,用最后一個參數來區別 1132 triggerHandler: function( type, data ) { 1133 if ( this[0] ) { 1134 return jQuery.event.trigger( type, data, this[0], true ); 1135 } 1136 }, 1137 1138 toggle: function( fn ) { 1139 // Save reference to arguments for access in closure 1140 var args = arguments, 1141 guid = fn.guid || jQuery.guid++, 1142 i = 0, 1143 toggler = function( event ) { 1144 // Figure out which function to execute 1145 var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i; 1146 jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 ); 1147 1148 // Make sure that clicks stop 1149 event.preventDefault(); 1150 1151 // and execute the function 1152 return args[ lastToggle ].apply( this, arguments ) || false; 1153 }; 1154 1155 // link all the functions, so any of them can unbind this click handler 1156 toggler.guid = guid; 1157 while ( i < args.length ) { 1158 args[ i++ ].guid = guid; 1159 } 1160 1161 return this.click( toggler ); 1162 }, 1163 // 模擬hover事件 1164 hover: function( fnOver, fnOut ) { 1165 return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); 1166 } 1167 }); 1168 // 綁定和注冊事件的快捷鍵,內部還是調用on()或者trigger() 1169 // 初始化jQuery.event.fixHooks 1170 jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + 1171 "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + 1172 "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { 1173 1174 // Handle event binding 1175 jQuery.fn[ name ] = function( data, fn ) { 1176 if ( fn == null ) { 1177 fn = data; 1178 data = null; 1179 } 1180 1181 return arguments.length > 0 ? 1182 // 綁定事件 1183 this.on( name, null, data, fn ) : 1184 // 觸發事件,不提供數據data 1185 this.trigger( name ); 1186 }; 1187 1188 1189 // keyHooks 1190 if ( rkeyEvent.test( name ) ) { 1191 jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks; 1192 } 1193 // mouseHooks 1194 if ( rmouseEvent.test( name ) ) { 1195 jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks; 1196 } 1197 });?
轉載于:https://www.cnblogs.com/lovesueee/archive/2012/11/20/2778745.html
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的【原创】jQuery1.8.2源码解析之jQuery.event的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: VC++2012编程演练数据结构《3》堆
- 下一篇: Codeigniter 用户登录注册模块
