摘要:專門為事件建立一個有問題,直接退出如果是一個事件處理對象,且有屬性。參考源碼分析事件體系結構解密事件核心綁定設計一解密事件核心委托設計二本文在上的源碼地址,歡迎來。
歡迎來我的專欄查看系列文章。
通過前面一章對于 addEvent 庫的介紹,它的兼容性超級棒,據說對于 IE4、5 都有很好的兼容性,這和 jQuery 的原理是一致的,而在 jQuery 中,有一個對象與其相對應,那就是 event。
上上章就已經說過了,這個 jQuery.fn.on這個函數最終是通過 jQuery.event對象的 add 方法來實現功能的,當然,方法不局限于 add,下面就要對這些方法進行詳細的介紹。
關于 jQuery.event這是一個在 jQuery 對象上的方法,它做了很多的優化事件,比如兼容性問題,存儲優化問題。
jQuery.event = { global = {}, add: function(){...}, remove: function(){...}, dispatch: function(){...}, handlers: function(){...}, addProp: function(){...}, fix: function(){...}, special: function(){...} }
上面是 event 上的一些函數,其中:
add() 是添加事件函數,在當前 dom 上監聽事件,生成處理函數;
remove() 移除事件;
dispatch() 是實際的事件執行者;
handlers() 在 dispatch 執行的時候,對事件進行校正,區分原生與委托事件;
addProp() 是綁定參數到對象上;
fix() 將原生的 event 事件修復成一個可讀可寫且有統一接口的對象;
special() 是一個特殊事件表。
說 add 函數之前,還是忍不住要把 Dean Edwards 大神的 addEvent 庫來品味一下,盡管之前已經談過了:
function addEvent(element, type, handler) { // 給每一個要綁定的函數添加一個標識 guid if (!handler.$$guid) handler.$$guid = addEvent.guid++; // 在綁定的對象事件上創建一個事件對象 if (!element.events) element.events = {}; // 一個 type 對應一個 handlers 對象,比如 click 可同時處理多個函數 var handlers = element.events[type]; if (!handlers) { handlers = element.events[type] = {}; // 如果 onclick 已經存在一個函數,拿過來 if (element["on" + type]) { handlers[0] = element["on" + type]; } } // 防止重復綁定,每個對應一個 guid handlers[handler.$$guid] = handler; // 把 onclick 函數替換成 handleEvent element["on" + type] = handleEvent; }; // 初始 guid addEvent.guid = 1; function removeEvent(element, type, handler) { // delete the event handler from the hash table if (element.events && element.events[type]) { delete element.events[type][handler.$$guid]; } // 感覺后面是不是要加個判斷,當 element.events[type] 為空時,一起刪了 }; function handleEvent(event) { // grab the event object (IE uses a global event object) event = event || window.event; // 這里的 this 指向 element var handlers = this.events[event.type]; // execute each event handler for (var i in handlers) { // 這里有個小技巧,為什么不直接執行,而是先綁定到 this 后執行 // 是為了讓函數執行的時候,內部 this 指向 element this.$$handleEvent = handlers[i]; this.$$handleEvent(event); } };
如果我們對這個瀏覽器支持 addEventListener,我們就可以對 addEvent 函數就行稍微對小修改(暫不考慮 attachEvent 兼容),在 addEvent 函數的最后,如果代碼修改成以下:
//element["on" + type] = handleEvent; if(!element.hasAddListener){ element.addEventListener(type,handleEvent,false); // 監聽事件只需添加一次就好了 element["hasAddListener"] = true; }
雖然以上的做法有點重復,需要對基本邏輯進行判斷,但這已經非常接近 jQuery 中的事件 add 方法。換句話說,以前的邏輯是把所有的監聽方法都通過 addEventListener 來綁定,綁定一個,綁定兩個,現在的思路變了:如果我們寫一個處理函數(handleEvent),這個處理函數用來處理綁定到 DOM 上的事件,并通過 addEventListener 添加(只需添加一次),這就是 jQuery 中事件處理的基本邏輯(我所理解的,歡迎指正)。
懂了上面,還需要清楚委托事件的本質:在父 DOM 上監聽事件,事件處理函數找到對應的子 DOM 來處理。
jQuery.event.add 函數分析好了,來直接看源碼,我已經不止一次的提到,學習源碼最好的方式是調試,最有效的調試是覆蓋率 100% 的測試用例。
jQuery.event = { global: {}, add: function( elem, types, handler, data, selector ) { var handleObjIn, eventHandle, tmp, events, t, handleObj, special, handlers, type, namespaces, origType, // jQuery 專門為事件建立一個 data cache:dataPriv elemData = dataPriv.get( elem ); // elem 有問題,直接退出 if ( !elemData ) { return; } // 如果 handler 是一個事件處理對象,且有 handler 屬性 if ( handler.handler ) { handleObjIn = handler; handler = handleObjIn.handler; selector = handleObjIn.selector; } // Ensure that invalid selectors throw exceptions at attach time // Evaluate against documentElement in case elem is a non-element node (e.g., document) if ( selector ) { jQuery.find.matchesSelector( documentElement, selector ); } // guid。 if ( !handler.guid ) { handler.guid = jQuery.guid++; } // 初始化 data.elem 的 events 和 handle if ( !( events = elemData.events ) ) { events = elemData.events = {}; } if ( !( eventHandle = elemData.handle ) ) { eventHandle = elemData.handle = function( e ) { // 最終的執行在這里,點擊 click 后,會執行這個函數 return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? jQuery.event.dispatch.apply( elem, arguments ) : undefined; }; } // 處理多個事件比如 ("click mouseout"),空格隔開 types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; 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; } // 特殊處理 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 ); // 如果 click 事件之前沒有添加過, if ( !( handlers = events[ type ] ) ) { handlers = events[ type ] = []; handlers.delegateCount = 0; // Only use addEventListener if the special events handler returns false // addEventListener 事件也只是添加一次 if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { if ( elem.addEventListener ) { // eventHandle 才是事件處理函數 elem.addEventListener( type, eventHandle ); } } } if ( special.add ) { special.add.call( elem, handleObj ); if ( !handleObj.handler.guid ) { handleObj.handler.guid = handler.guid; } } // 添加到事件列表, delegates 要在前面 if ( selector ) { handlers.splice( handlers.delegateCount++, 0, handleObj ); } else { handlers.push( handleObj ); } // 表面已經添加過了 jQuery.event.global[ type ] = true; } } }
來從頭理一下代碼,之前就已經說過 jQuery 中 Data 的問題,這里有兩個:
// 用于 DOM 事件 var dataPriv = new Data(); // jQuery 中通用 var dataUser = new Data();
dataPriv 會根據當前的 elem 緩存兩個對象,分別是 events 和 handle,這個 handle 就是通過 addEventListener 添加的那個回掉函數,而 events 存儲的東西較多,比如我綁定了一個 click 事件,則 events["click"] = [],它是一個數組,這個時候無論我綁定多少個點擊事件,只需要在這個數組里面添加內容即可,添加的時候要考慮一定的順序。那么,數組的每個子元素長什么樣:
handleObj = { type: type, origType: origType, data: data, handler: handler, guid: handler.guid, selector: selector, needsContext: selector && jQuery.expr.match.needsContext.test( selector ), namespace: namespaces.join( "." ) }
兼容性,兼容性,兼容性,其實 add 事件主要還是考慮到很多細節的內容,如果把這些都拋開不開,我們來比較一下 event.add 函數和 addEvent 函數相同點:
// 左邊是 addEvent 函數中內容 addEvent == jQuery.event.add; handleEvent == eventHandle; handler.$$guid == handler.guid; element.events == dataPriv[elem].events;
非常像!
jQuery.event.dispatch 函數分析當有 selector 的時候,add 函數處理添加事件,而事件的執行,要靠 dispatch。舉個例子,在 $("body").on("click","#test",fn),我們點擊 body,會被監聽,dispatch 函數是會執行的,但是 fn 不執行,除非我們點擊 #test。大概 dispath 就是用來判斷 event.target 是不是需要的那個。
jQuery.event.extend( { dispatch: function( nativeEvent ) { // 把 nativeEvent 變成可讀寫,jQuery 認可的 event var event = jQuery.event.fix( nativeEvent ); var i, j, ret, matched, handleObj, handlerQueue, args = new Array( arguments.length ), // 從 data cache 中搜索處理事件 handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [], special = jQuery.event.special[ event.type ] || {}; // Use the fix-ed jQuery.Event rather than the (read-only) native event args[ 0 ] = event; for ( i = 1; i < arguments.length; i++ ) { args[ i ] = arguments[ i ]; } 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; } // 對 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; // 按照 handlers 排好的順序,一次執行 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.rnamespace || event.rnamespace.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 改造后的事件 event.preventDefault(); event.stopPropagation(); } } } } } // Call the postDispatch hook for the mapped type if ( special.postDispatch ) { special.postDispatch.call( this, event ); } return event.result; } } );
event.handlers的邏輯也是十分復雜的,而且我看了,也沒看懂,大致就是將所有綁定到 elem 上的事件,按照一定的順序來區分他們的執行順序。
總結花了好幾天,也只是看了一個皮毛而已,尤其是對其中的事件邏輯,感覺好復雜。也只能這樣了。
參考jQuery 2.0.3 源碼分析 事件體系結構
解密jQuery事件核心 - 綁定設計(一)
解密jQuery事件核心 - 委托設計(二)
本文在 github 上的源碼地址,歡迎來 star。
歡迎來我的博客交流。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/86832.html
摘要:專題系列共計篇,主要研究日常開發中一些功能點的實現,比如防抖節流去重類型判斷拷貝最值扁平柯里遞歸亂序排序等,特點是研究專題之函數組合專題系列第十六篇,講解函數組合,并且使用柯里化和函數組合實現模式需求我們需要寫一個函數,輸入,返回。 JavaScript 專題之從零實現 jQuery 的 extend JavaScritp 專題系列第七篇,講解如何從零實現一個 jQuery 的 ext...
摘要:而事件委托的概念事件目標自身不處理事件,而是將其委托給父元素或祖先元素或根元素,而借助事件的冒泡性質由內向外來達到最終處理事件。而且一旦出現,局部刷新導致重新綁定事件。函數的用法,代表要移除的事件,表示選擇的,表示事件處理函數。 歡迎來我的專欄查看系列文章。 這次的內容是來介紹關于 jQuery 的事件委托。不過在之前呢有必要先來了解一下 JS 中的事件委托與冒泡,我之前也寫過類似的博...
摘要:作者按因為教程所示圖片使用的是倉庫圖片,網速過慢的朋友請移步系列教程十三自動生成文件原文地址。編寫配置文件老規矩,是在這個選項中配置的。更多資料文檔文檔系列教程十三自動生成文件原文地址 作者按:因為教程所示圖片使用的是 github 倉庫圖片,網速過慢的朋友請移步《webpack4 系列教程(十三):自動生成 HTML 文件》原文地址。更歡迎來我的小站看更多原創內容:godbmw.co...
摘要:作者按因為教程所示圖片使用的是倉庫圖片,網速過慢的朋友請移步系列教程十三自動生成文件原文地址。編寫配置文件老規矩,是在這個選項中配置的。更多資料文檔文檔系列教程十三自動生成文件原文地址 作者按:因為教程所示圖片使用的是 github 倉庫圖片,網速過慢的朋友請移步《webpack4 系列教程(十三):自動生成 HTML 文件》原文地址。更歡迎來我的小站看更多原創內容:godbmw.co...
摘要:而存在的意義就是保證請求或響應對象可在線程池中被解碼,解碼完成后,就會分發到的。 2.7大揭秘——服務端處理請求過程 目標:從源碼的角度分析服務端接收到請求后的一系列操作,最終把客戶端需要的值返回。 前言 上一篇講到了消費端發送請求的過程,該篇就要將服務端處理請求的過程。也就是當服務端收到請求數據包后的一系列處理以及如何返回最終結果。我們也知道消費端在發送請求的時候已經做了編碼,所以我...
閱讀 641·2021-11-24 09:39
閱讀 3485·2019-08-30 15:53
閱讀 2519·2019-08-30 15:44
閱讀 3245·2019-08-30 12:54
閱讀 2212·2019-08-29 12:23
閱讀 3309·2019-08-26 14:05
閱讀 2111·2019-08-26 13:36
閱讀 3441·2019-08-26 13:33