成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

jQuery 源碼系列(十三)事件處理源碼

劉厚水 / 742人閱讀

摘要:專門為事件建立一個(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

相關(guān)文章

  • JavaScript專題系列文章

    摘要:專題系列共計(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...

    Maxiye 評(píng)論0 收藏0
  • jQuery 源碼系列(十一)event 總體概述

    摘要:而事件委托的概念事件目標(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ò)類似的博...

    liujs 評(píng)論0 收藏0
  • webpack4 系列教程(十三):自動(dòng)生成HTML文件

    摘要:作者按因?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...

    superw 評(píng)論0 收藏0
  • webpack4 系列教程(十三):自動(dòng)生成HTML文件

    摘要:作者按因?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...

    quietin 評(píng)論0 收藏0
  • dubbo源碼解析(四十七)服務(wù)端處理請(qǐng)求過(guò)程

    摘要:而存在的意義就是保證請(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)做了編碼,所以我...

    yzzz 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<