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