JavaScript事件詳解-jQuery的事件實現(三)

来源:http://www.cnblogs.com/leomYili/archive/2016/12/23/6215021.html
-Advertisement-
Play Games

正文 本文所涉及到的jQuery版本是3.1.1,可以在壓縮包中找到event模塊。該篇算是閱讀筆記,jQuery代碼太長。。。。 Dean Edward的addEvent.js 相對於zepto的event模塊來說,jQuery的event那真是難讀了很多,先從大神Dean Edward的addE ...


正文

本文所涉及到的jQuery版本是3.1.1,可以在壓縮包中找到event模塊。該篇算是閱讀筆記,jQuery代碼太長。。。。

Dean Edward的addEvent.js

相對於zepto的event模塊來說,jQuery的event那真是難讀了很多,先從大神Dean Edward的addEvent開始入手吧,地址在這裡。源碼不長

function addEvent(element,type,handler){
  if(element.addEventListener){
    element.addEventListener(type,handler,false);
  }else{
    if(!handler.$$guid) handler.$$guid = addEvent.guid++;
    if(!element.events) element.events = {};
    var handlers = element.events[type];
    if(!handlers){
      handlers = element.events[type] = {};
      if(element["on"+type]){
        handlers[0] = element["on"+type];
      }
    }
    handlers[handler.$$guid] = handler;
    element["on"+type]=handleEvent;
  }
}

作為主要的addEvent()部分,直接看不支持addEvenetListener的地方,可以看出,其對於事件句柄,handler作了處理,新增了$$guid的屬性,在remove的時候會很方便。同時,在函數內部,以handlers來簡化位元組,實質上仍然操作的是element.events,然後使用"onXXX"的方式,來傳遞handleEvent()綁定事件。

在handleEvent中,

function handleEvent(event) {
  var returnValue = true;
  event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event);
  var handlers = this.events[event.type];
  for (var i in handlers) {
    this.$$handleEvent = handlers[i];
    // 執行
    if (this.$$handleEvent(event) === false) {
      returnValue = false;
    }
  }
  return returnValue;
};

可以看到,把addEvent中處理過的events進行使用,這裡的this因為在addEvent()中使用了element["on"+type]=handleEvent,所以this在觸發操作時變為element,當然,還有修複對象的fixEvent。
在對addEvent的評論中,建議元素分配一個UUID,所有回調放到一個對象中存儲,也就是把events給拋開,而使用緩存來存儲對應元素的回調列表。

jQuery的event

如果和上一篇JavaScript事件詳解-zepto的事件實現(二)相比較而言,在3.1.1版本中,入口推薦用on:

$("#btn").on("click",function(event){
  console.log(event);
});

36
可以看到event對象已經發生了變化,相對於zepto生成的一個新的事件對象而言,jQuery的event對象是重新修改的一個內部對象。

入口

37
38

跟事件綁定有關的入口,可以看出,bind和delegate內部仍然是用的on方法。

如果按上述的例子,那麼在給$("#btn")註冊click事件時,會通過jQuery.fn.on方法,然後調用jQuery內部的on()函數,

function on(elem,types,selector,data,fn,one){...}

elem參數不用說,在調用on()函數時,傳了this過去,也就是$("#btn"),對外部開放的介面里,只有四個參數types,selector,data,fn。也因此,先從這四個參數入手:

  • types,不如說event types更恰當,也就是上面例子中傳的'click',可以用空格來分隔,一次傳入多個事件類型
  • selector,用於事件委托的選擇符參數
  • data,當一個事件被觸發時要傳遞的data給事件處理函數,在回調的event中會有該屬性,以便於使用
  • fn,回調,事件句柄
    在on()函數里,首先分層:
    41

可以看到,省略的幾個判斷中,都是對於這些參數做的處理,第一個省略處是對於types的處理,其中對如果types是map類型的值做了處理,再次開始on()函數。
第二個省略處則是對於參數的簡略使用,在代碼中,可以看到其註釋展示的三種情況:

// (types,fn)
// (types,selector,fn)
// (types,data,fn)

也相當於在使用時,我們可以略去data和selector來簡單的完成一次綁定。
而第三處則是對one的判斷,也就是在回調中加入了off(),即調用一次回調,立刻off event。
on()只是對於參數的處理,接下來就是使用jQuery.event.add()來再次進行處理。

如果說jQuery.Event是對event對象的校正,那麼jQuery.event則是提供了內部方法:
42

首先是add(elem,types,handler,data,selector):
為什麼add才是開始監聽,因為只有在這裡才能找到addEventListener,DOM0級的onXXX,和DOM2級的addEventListener,IE的attachEvent,來作為切入點。
在函數的初始,首先開始獲取elemData=dataPriv.get(elem),從字面意思看,這個應該是內部的緩存,也就是上面對於addEvent()所提到的緩存的回調列表。
作為緩存系統,確實有獨到之處。

cache

本來應該重開一章的,但是重點還是應該放到事件處理上,所以只是結合jQuery 2.0.3 源碼分析 數據緩存來說下自己的理解,以及相應的佐證。
首先就是內部的Data()函數,
43
(3.1.1版本)

這裡確實不應該,最近一段時間任務蠻多的,也沒有仔細讀源碼,看了一些文章後,先入為主的去找全局緩存去了,找啊找,還是too young too simple。對比一下也知道有問題撒。

44
(2.1.1版本)

才發現3.1.1版本中的Data已經沒有了cache,之前會將事件存入cache中,只給每個dom節點一個uid來當作鑰匙,獲取數據,雖然兩個版本大體結構都是創建了一個Data類,具有get,set方法,果然好久不打java,把面向對象全還給老師了,但不同的是3.1.1版本中則直接使用:
45

將其存入dom節點之中,這裡看來需要深入理解下了,為什麼3.0版本會把數據存入節點中?
46

我們再回到上文,繼續add()。

jQuery.event.add

現在就可以知道這個elemData從何處獲取到事件。

初始狀態第一次添加綁定時,所獲取到的肯定是個空對象,而第二次再次綁定就可以拿到第一次綁定的行為的值,註意傳的參數是elem
47

然後除了對selector進行處理之外,還給事件句柄添加唯一的guid,看到上圖,也知道在之後的處理中,會給elemData這個對象增加兩個新的鍵:events和handle。
其中handle
48

可以看到這裡修改事件句柄,所以addEvenetListener時,回調會從jQuery.event.dispatch中來觸發。
這裡的handlers僅僅是內部對象,用來建立內部隊列。
add()函數剩下的則是根據參數的types的長度(“分隔符為空格”)來修改elemData.events的值,並且在事件初次綁定時,執行addEventListener()
49

這裡有個special

50

可以看到這個對象中存入的其實是一些特殊的事件,每個事件都會有一些定義的屬性,用於綁定,或判斷.

if(!special.setup || special.setup.call(elem,data,namespaces,eventHandle) === false){
  if(elem.addEventListener){
    elem.addEventListener(type,eventHandle);
  }
}

進行綁定。

  1. special.setup:初始化綁定
  2. specia.delegateType : speacial.bindType:事件類型,在handleObj中會傳入保存到dom節點里。
  3. trigger:內部觸發時使用的事件句柄,51
  4. _default:預設操作相容
  5. handle:52
    只有這四個事件會有handle,同樣是觸發時的事件句柄
  6. noBubble:防止image.load事件冒泡到window.load事件
  7. preDispatch:在dispatch中截斷,來執行該事件句柄
  8. postDispatch:在dispatch內部的事件隊列執行完畢之後,來執行該事件句柄
  9. add:添加事件綁定
  10. remove:解除事件綁定

之後就是handleObj

這個內部的對象保存了之後存入events中相應事件的值
53

可以看到,這裡使用了jQuery的extend來擴展,可能存在的handleObjIn作為補充對象。

開始調用原生addEventListener進行監聽

54
大部分還是會走原生事件監聽方法,這裡的handlers很有意思,不同於zepto使用handles作為內部隊列,因為jQuery有緩存機制。

handlers = events[type] = [];
handlers.delegateCount = 0;

從這裡可以得出在add內部,handlers引用的是events[type],並且這個指針有一個delegateCount的屬性,而在add的最後部分
55
將其按順序推入處理列表中。因為只有滿足selector存在的情況下,delegateCount才會開始增加,所以之後的handlers函數中可以看到相應的處理。

jQuery.event.global[type] = true

這裡倒是不知道什麼意思,不過至少記錄了所有的監聽事件名稱

jQuery.event.dispatch

remove其實和觸發的事件流沒有什麼關係,所以還是以事件觸發流程開始分析。
上文的add中,將事件句柄做了一次處理:

return typeof jQuery !== "undefined" && jQuery.event,triggered !== e.type ? jQuery.event.dispatch.apply(elem,arguments)

可以看到,dispatch一開始就會使用jQuery.event.fix(nativeEvent)來進行event對象修正。

jQuery.event.fix 或者更直接是jQuery.Event()

56
可以看到,處理之後event就變為jQuery.event,fix只是檢查相應對象上,是否有緩存對象,否則就新建一個Event類的實例。
不過3.1.1版本和2.1.1有點區別:

this.target = (src.target && src.target.nodeType === 3) ? src.target.parentNode : src.target;
this.currentTarget = src.currentTarget;
this.relatedTarget = src.relatedTarget;

修複文字不應該成為觸發節點。

新增的jQuery.event.addProp()方法

57

可以看到,3.1版的event對外提供了event對象上所有屬性的getter,setter方法
58
而在jQuery.Event的原型鏈中,其constructor被指回給了自己
59
還有相應的阻止冒泡,阻止預設動作的方法
其中simulate在8400多行,為focus(in | out)提供觸發。
isSimulated則處理這兩個方法的阻止動作。

重新回到dispatch方法,校準過event之後,在內部定義的變數中,我們可以看到
60

這裡又會去拿elem緩存中對應的事件,這裡就是之前add時存入elem節點中的數據。
然後使用jQuery.event.handlers來組建事件隊列,

先看事件隊列處理完之後
61
其中:

ret = ((jQuery.event.special[handleObj.origType] || {}).handle || handleObj.handler).apply(matched.elem,args);

會執行回調函數。

dispatch中簡單的邏輯說完了,如果以簡單的綁定事件而言,已能夠完成功能。但如果僅止於此,那麼自然對不起jQuery事件模塊的那麼多行代碼。

從全局角度來看jQuery.event

首先,用戶通過註冊jQuery.on方法開始生成事件,然後是add中dataPriv.get(elem)獲取或設置緩存
並對handler做處理,並設置唯一的guid,在觸發事件dispatch中,首先使用fix方法校準事件,然後生成事件隊列。逐個開始執行
62

jQuery.event.handlers

64

可以看到,如果delegeteCount為0,也就是沒有委托,中間的處理則直接略過,其中的cur=event.target,即觸發動作的節點,而在下麵,很明顯的cur又被指成了elem,返回的handlerQueue其實只是簡單的將傳入的參數做了個組裝。
65

事件委托

簡單的事件其實對於隊列的要求不高,只要返回之後執行就好,但事件委托則不是這樣,之前也介紹了zepto和原生事件里對於事件委托的處理,但jQuery的委托機制又格外不同。

在介紹jQuery.event.add()時的addEventListener(type,eventHandle),所傳的事件句柄
63

重新寫個事件委托的例子

$(".jumbotron").on("click","#test",function(e){
  console.log("test");
}).on("click",function(e){
  console.log("demo")
})

接著上面的handlers事件來講,當有委托元素,也就是selector不為空時,則進入判斷體之中:

if(delegateCount && cur.nodeType && !(event.type === "click" && event.button >=1 ))

防止火狐中右鍵或中鍵點擊時,會冒泡到document的click事件,
然後是

for(;cur !== this;cur = cur.parentNode || this)

cur會不斷的往上遍歷,去尋找綁定事件的節點,這裡通過迴圈來模仿冒泡機制,
66
然後查找節點,將回調函數加入到matchedSelectors之中,並返回經過驗證的事件隊列。
如果按照上面那個例子來說,點擊#test元素時,所生成的事件隊列就是:
67
而在執行相關的事件時,也會從test元素先開始觸發回調,然後是jumbotron

也因此,會發現其實都是一樣的思路,在委托元素上,都是判斷觸發的元素是否在綁定的元素之中,再由內而外的開始執行事件。
當然,在執行事件隊列時,也要註意,jQuery這裡的處理方法
68
能夠使得回調中的this就是selector的節點。
而各種處理相容性的special事件,則更需要對各個瀏覽器操作時的差異有足夠的理解。

小結

這次的閱讀不大順利,中間工作比較忙,自己也確實是比較懶,所以進展比較慢,像模擬事件都沒有想好怎麼寫,不過也確實讓自己多想想該怎麼歸納知識點,中間很多的思緒和時間都浪費掉了,看來還是單獨分小章節寫比較適合節奏。



您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • display:box 使子元素成行排列如果父級寬度小於子級盒子 不會把超出部分擠出下麵 而是直接超出 -box-orient:vertical 使盒子垂直顯示 預設水平顯示 -box-direction:Reverse使盒子排列順序顛倒; -box-ordinal-group: ;設置元素排列的具 ...
  • 快速排序(Quicksort)是對冒泡排序的一種改進。快速排序由C. A. R. Hoare在1962年提出。 它的基本思想是:通過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要小,然後再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以 ...
  • 1、冒泡排序 2、快速排序 3、直接插入排序 4、希爾排序 5、直接選擇排序 ...
  • 語法對比: Promise .then(f).catch(f)是.then(f,f)的語法糖 .all([A,B,C])等最慢的 .race([A,B,C])最快的 $.Deferred .done(f).fail(f)是.then(f,f)的語法糖 .when(A,B,C)等最慢的 無類似race ...
  • 2016.12.16 1.cick事件調用的函數中加入touchmove等事件會發生事件穿透,詳情(http://www.tuicool.com/articles/6NfaUnM) 移動設備的click事件有300ms延遲,用於判斷是否雙擊 2.ios設備會有一個預設的css樣式,如input按鈕在 ...
  • 伴隨網路時代日新月異的發展,用戶不僅僅滿足於軟體系統的功能需求,對軟體系統的頁面顯示效果以及交互模式的要求也逐漸提高。尤其是展示性質的平臺頁面對於界面美化效果要求更高,有一句話說的好:Html是結構,CSS是裝飾,JS是膠水(動態設定CSS)。今天我們來介紹一些網站美化常見的CSS樣式以及處理手法。 ...
  • h5的js游戲框架JY的2.0版介紹,本文作重介紹了通過jy1.x做的h5游戲和通過jy2.x做的h5有啥區別,然後用大頭吃小頭這個app游戲來作實踐。 ...
  • 浮動與清除浮動 一、float:主要目的是為了實現文本繞排圖片的效果。 也成了創建多欄佈局最簡單的方式。 <img src= “ ” /> <p>文本內容段落內容文本內容段落內容文本內容段落內容文本內容段落內容文本內容段落內容</p> 【1】文本繞排圖片 P {margin: 0; border: ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...