能夠結合上一篇JavaScript事件詳解-原生事件基礎(一)綜合考慮
源碼暫且不表,github裏還有中文網站都能下到最新版的zepto。整個event模塊不長,274行,咱們能夠看到,整個event模塊,事件綁定核心就是on和off,還有一個trigger用來觸發,類觀察者模式,能夠先看看湯姆大叔的深刻理解JavaScript系列(32):設計模式之觀察者模式,其他皆爲實現的處理函數。
首先來個demo:javascript
$("#btn").on("click",function(event){ console.log(event); })
一個簡單的click事件監聽示例。
根據event模塊中對於事件的使用來看:
css
能夠看到,綁定函數有五個參數:html
根據參數,咱們能夠很輕易的將on分爲幾部分(上圖所示):java
autoRemove,若是one爲true,也就是隻想使用一次,那麼使用remove,並經過apply,給callback設立event對象;
而delegator中,若是selector是綁定元素的子節點,zepto以event.target爲目標元素,判斷是否觸發節點的父級和傳入的selector一致,上下文是遍歷以後的節點。而後建立一個該事件對象的副本(createProxy),返回compatible()函數處理的event,固然,最後都會經過add()來進行註冊:
ios
首先是zid,zepto裏面有個handlers對象,用於存放處理過的事件對象,_zid初始值爲1,每次會按照值存入handlers,而且修改event對象中的_zid,每次存入的是一次綁定的全部事件:
css3
由於每次使用$()建立的zepto對象都是新的,用handlers創建隊列才能更好的進行管理。
以後就是對於以空白字符形式(/\s/)進行分割的字符串的處理,內部建立了handle,注意其parse方法是內部方法,而不是Date.parse()。git
前面也說過,冒泡事件會有反作用,mouseover和mouseout,若是隻是簡單的節點,沒有問題,但有了子節點以後。原先監聽父節點的事件,會在鼠標移過去時再次觸發。這是因爲監聽的是整個父節點,而移動到子節點時,子節點並無事件,因此向上冒泡所形成的bug,而在DOM3級中,新定義了兩個不冒泡的事件:mouseenter,mouseleave,使用這兩個事件,能夠解決這一問題。而在zepto中,使用了relatedTarget屬性,並使用contains判斷觸發的節點在不屬於移出(mouseover),移入(mouseout)時,才執行回調。且對不支持mouseenter和mouseleave事件的狀況進行了兼容。github
而後就是調用addEventListener,開始監聽,這裏沒有作IE的兼容。事件句柄隨着handler插入handles中,爲以後的remove作準備。這裏的proxy是對於event的擴充,也是添加了當return false時,調用
preventDefault()和stopPropagation()。chrome
能夠看出來,和on是對應的寫法,一樣能夠分三部分,只不過這裏的功能是移除監聽而已。設計模式
就直接到了remove,這裏主要作的就是根據傳入的event和selector,用findHandlers進行查找,而後刪除handlers中對應的事件,同時調用removeEventListener來移除事件處理程序。
這裏使用的是createEvent()和initEvent(),這裏的事件類型若是不是specialEvents中定義的MouseEvents,就會變成默認Events(DOM3)。
看下自定義的事件對象:
能夠發現,由於使用compatible()封裝了一下event,因此會有zepto新增的屬性,以及咱們傳入的props屬性。
咱們知道,DOM3級中提供的觸發事件的api是dispatchEvent()(低版本IE中是fireEvent()),而zepto這裏也是同樣:
能夠看出,對於參數event爲對象時進行了處理,意味着能夠直接使用trigger建立+觸發,支持dispatchEvent的狀況下,會直接觸發,若是不是DOM節點,則使用triggerHandler()來觸發。
能夠看出,若是節點在以前綁定了其餘事件處理程序且使用過stopImmdiatePropagation(),則也不會再觸發自定義事件。
demo:
這實際上是個獨立的函數,在add()函數中使用的proxy,是handler.proxy()函數,與這個無關。這個函數起到的做用很相似extend,只不過它擴展的是上下文(執行環境)。
我卻是以爲,最關鍵的就是
fn.apply(fn.apply(context, args ? args.concat(slice.call(arguments)) : arguments)) $.proxy.apply(null,args);
zepto實際是爲了能在手機上輕量級的使用而創造的,爲了使用上的不卡頓,手機上不能用click事件,有延時,緣由很少說了,寫個小demo,能夠看看pc和手機的點擊事件耗時區別:
touch和click demo
若是pc端,最好用chrome,沒大考慮兼容。用開發者工具也能模擬touch事件。
因此zepto提供了touch模塊,我先本身模擬了下tap事件,連着又自定義了一些事件:
touch和tap demo
個人思路是,touch事件觸發的很快,那麼就用touch事件來模擬click,以touchstart和touchend爲開始和結束,只考慮了單指的狀況,touch事件自己就有:
我沒有把touch的屬性填入自定義的事件裏,就一層,因此也沒考慮冒不冒泡了,後面能夠完善下,tap卻是還好,dbltap稍稍耗了點時間。
整個touch模塊也很簡單,仍是先從入口開始:
能夠看到zepto新增了這些事件,並作了簡寫的處理,整個部分最重要的是給document綁定了touch,MSpointer,pointer,MSGestureEvent的觸摸事件,不過上面的
來看,應該有setTimeout,下面的cancelAll()中也有使用clearTimeout。
先看touch事件,其餘的反正是兼容。
zepto定義的局部變量touch中有四個值,x1,x2,y1,y2,應該是用來記錄第一個觸發的點和第二個觸發的點,果真在監聽touchmove事件的回調裏,使用了兩個點來計算偏移,不過這裏是將途中全部偏移量都與初始值進行比較,而後彙總。
在touchend中,對於swipe(滑動)和tap(點擊)進行了處理,由於deltaX和deltaY須要在30*30的範圍內纔會被觸發,可是它的偏移量是move移動的總和,因此在觸發時容錯率低,也就是很差點出來,相比較其餘操做而言。
在touchcancel的處理中,清除全部延遲操做。
同時其整個操做實際上是綁定在了document上,因此使用時若是有其餘的touch事件也綁定在了doucment上,而且取消了冒泡事件,則以後的全部操做都會失效。
專門寫了下demo來測試點透問題,點透事件的發生。這裏也是以前的click300ms的延時帶來的問題,若是最上層始終存在還好,就怕是點擊消失的狀況,那麼若是上層用的是touch事件,下層是a標籤,input或者綁定了click事件的節點,則也會被觸發,只能說zepto的touch事件還須要咱們本身來擴充和完善。
在經過對javaScript事件的詳細學習以後,仍是有不少途徑去解決這個問題的,好比:
使用十分簡單,只要FastClick.attach(document.body);
就好,在github上也介紹了另兩種方法,能夠不使用fastClick來快速點擊。
具體的能夠直接去它的github地址看https://github.com/ftlabs/fastclick。
我修改了下以前的例子,新增了一個點擊事件的例子,能夠看出,基本比touchend慢1ms。並且使用了click事件,因此也不存在點透事件。
一樣從入口進入,在代碼內部,先實例化FastClick(),它有兩個參數:layer和options,layer也就是咱們以前傳的document.body,
在FastClick()函數中,能夠先找到
至關於document.body上的節點的click,touch事件的監聽都會被FastClick內部的事件給處理掉,若是layer上有onclick事件,一樣會被oldOnClick複寫。
在pc上不用多說,綁定操做的節點確定先經過對click的監聽來處理,
這裏的trackingClick應該是若是以前頁面中的touchend被UI事件阻塞(能夠簡單模擬下,也就是出現字符被選中的狀況),從新置空它。
而在手機上,最早觸發的操做一定是touchstart和touchmove,touchend。
這裏的targetElement很重要,實際上是上面提過的event的target,實際觸摸的節點目標,這個屬性會在接下來的move,end中用來判斷是否中間換過節點,並在sendClick中用來真實的觸發事件
返回touchstart中,兼容先不看,這裏對於初始節點也作了存儲,touchStartX,touchStartY
。
而接下來的對於double-tap的處理,其實須要看需求,若是是默認值,那麼200ms內的第二次點擊會禁止觸發其默認事件event.preventDefault()
,在手機上就沒法觸發雙擊事件。
而後就是touchmove,這裏卻是很短,只是對於targetElement作了個判斷,是不是同一個觸摸節點。
在touchend中,一樣略過兼容不看,不過我卻是又學到了一個原生api,document.elementFromPoint
,傳入x,y來找到節點,只是認識了一下,實不實用不表。
其實一共有三種方式來操做fastclick內部自定義的事件,不過當targetTagName=label時,這裏focus了一下,把這裏的觸發放到了needsClick中而已,而在這兩塊中,能夠知道,若是給className中加入needsFocus
和needsClick
,則會中斷fastclick,使用原生事件:
needsFocus:第一個判斷事件從touchstart到touchend是否超過100ms,則將targetElement置空,其實就是迴歸原來的方法,取消fastclick的事件。而後就是focus事件
這裏使用了setSelectionRange(length,length),先選中文本。
有興趣的能夠去MDN看,不過我測試了一下,上面的例子中有個input,其中確實會有http://www.cnblogs.com/vajoy/p/5522114.html#!/follow提到的問題,在快速點擊時光標會直接移到最後面,而上面傳入的參數都是length,天然就跑到了最後面。
再經過sendClick來觸發,就會有問題,並且在觸發完自定義click以後,
取消了咱們點擊時帶的focus操做,這裏我先註釋了一下代碼,果真,光標先定到末尾,再定到正確位置,雖然上述博主的方法暫緩此問題,但在有些瀏覽器中仍然存在這一問題,而且實際上仍然是光標定到末尾,再定到正確位置,不如直接不用模擬的focus,做者本身的註釋裏說的是ios7中有問題,但focus中用的是deviceISIOS,而不是deviceIsIOSWithBadTarget,改這裏也能解決問題。
needsClick:這裏首先preventDefault,再使用sendClick,上面稍微介紹了下,這裏模擬的都是MouseEvents事件,使用targetElement來觸發。
那麼爲什麼fastclick不穿透呢,我天然仍是找代碼中哪裏用了preventDefault和stopImmediatePropagation,在onMouse中,仍是防止快速點擊而加的阻止操做,實際上阻止點透的仍然是needsClick中的event.preventDefault()
,去掉以後,頁面點擊仍然會點透。
知道了仍是preventDefault()起了做用,咱們再回頭看touch模塊,對比一下fastclick,其實二者的主體層都是差很少的,document和document.body實際在大多數狀況下,可點擊域都是同樣,也就是說二者都綁定到了最外層上(近似),但fastclick提供了layer和options,意味着能夠規避風險,而zepto的touch模塊則直接綁定到了document之上,至少在使用上,並無fastclick方便,不過其定義的各類touch動做,頗有意義,在上面說過,在zepto解決點透,能夠:
$("#btn").tap(function(){ // do something }).on('touchend',function(e){ e.preventDefault(); });
在讀源碼時,layer綁定的部分,上面有圖,確實會誤導。由於實際上onClick是能夠不走的,之因此在手機上觸發的仍然是click事件,是由於在sendClick裏直接使用dispatchEvent()觸發了click,因此纔會從fastclick定義的this.onClick()中走,其實這一部分能夠和zepto同樣放入touchend模塊裏面。
這麼看,兩個代碼的核心其實大體相同,所不一樣的是fastclick中還加入的tap事件的focus方法,若是在zepto的touch中直接加入preventDefault(),則input沒法得到焦點,因此能夠引入focus事件,從而解決問題
可能會有兼容性的問題,對於blur()和focus()寫完jQuery以後能夠加入兼容性的寫法。
如今就不會點透了。
還能夠把touchstart,touchend操做放到具體的節點上,而後在節點中進行處理,不過這樣會改動的比較大。zepto中有個方法$.proxy,放在這裏有奇效,上面介紹過。
至於說delegate,undelegate,live,die這些代理事件,還有bind,unbind等綁定操做,其實都是on在起做用,不細說。中間去看了fastclick,以及其餘事情,其實星期一就寫完了,仍是基礎薄弱啊。
以後是看看jQuery的事件操做,壓力山大。