摘要:再看這個源碼的過程中,因為對事件類型的不充分,導(dǎo)致學(xué)習(xí)起來有些費勁,所以在講這個板塊之前先對一些事件進行了解。尋找元素上所有的句柄,我們在之前提到過這是真的回調(diào)函數(shù),如果有,則終止遍歷。參考文章與為何這般糾纏不清讀源碼之模塊文檔
為什么要看Zepto的源碼,因為公司用的是這個。。。。
再看這個源碼的過程中,因為對事件類型的不充分,導(dǎo)致學(xué)習(xí)起來有些費勁,所以在講這個板塊之前先對一些事件進行了解。
下面是觸發(fā)點擊事件的代碼,我們在inner上添加點擊事件,在wrapper添加事件,點擊inner都會觸發(fā)click事件。但這種情況需要我們每次都去點擊回調(diào)函數(shù)才會執(zhí)行,有沒有函數(shù)不需要我們手動觸發(fā),自動觸發(fā)呢?
wrapperinner
這里用到了一個需要用到一個API:
createEvent,具體代碼如下:
let event = document.createEvent("Event") event.initEvent("click", true, true) $inner.dispatchEvent(event)
這里我們通過createEvent創(chuàng)建了一個事件,并且其后必須馬上進行初始化,然后通過dispatchEvent進行事件分發(fā),這樣就用js代碼進行事件的觸發(fā),而不需要我們進行點擊才能觸發(fā)。
事件模擬在event模塊中有這么一段代碼
focus = {focus: "focusin", blur: "focusout"}, hover = {mouseenter: "mouseover", mouseleave: "mouseout"}
focus和blur我們都知道,但是為什么要重新隱射focusin和blur事件呢,在mdn中我們可以看到focus和focusin的區(qū)別在于focus不支持事件冒泡,如果不支持事件冒泡,那么帶來的效果就是不能夠進行事件委托。
同樣的mouseenter和mouseleave也不支持事件冒泡,但是mouseenter會帶來巨大的性能消耗,所以我們常用mouseover進行mouseenter進行事件的模擬。在鼠標(biāo)事件中,有一個relatedTarget事件,在前面提到因為mouseover支持冒泡,那該如何來模擬mouseenter事件呢。relatedTarget事件屬性返回的是和事件的目標(biāo)節(jié)點相關(guān)的節(jié)點。對于mouseover事件來說,該屬性是鼠標(biāo)指針移到目標(biāo)節(jié)點上所離開的那個節(jié)點。對于mouseout事件來說,該屬性是離開目標(biāo)時,鼠標(biāo)進入的節(jié)點。根據(jù)上面的描述,我們可以對relatedTarget的值進行判斷:如果值不是目標(biāo)元素,也不是目標(biāo)元素的子元素,就說明鼠標(biāo)已經(jīng)移入目標(biāo)元素而不是在元素內(nèi)部移動
var _zid = 1 function zid(element) { return element._zid || (element._zid = zid++) }
zid主要是用來標(biāo)記已經(jīng)綁定時間的元素,這個函數(shù)返回元素的_zid,如果沒有,那就全局的zid加一,并且賦值給元素的_zid屬性
parsefunction parse(event) { var parts=("" + event).split(".") return { e: parts[0], ns: parts.slice(1).sort().join(" ") } }
parse方法用來分解事件名和命名空間,{e: 事件名, ns: 命名空間},先把event變成字符串進行分割,得到事件名,和命名空間,命名空間可以為s1.s2.s3這種
compatible這是用來修正event對象中瀏覽器的差異
eventMethods = { preventDefault: "isDefaultPrevented", stopImmediatePropagation: "isImmediatePropagationStopped", stopPropagation: "isPropagationStopped" } function compatible(event, source) { if (source || !event.isDefaultPrevented) { source || (source = event) $.each(eventMethods, function(name, predicate) { var sourceMethod = source[name] event[name] = function(){ this[predicate] = returnTrue return sourceMethod && sourceMethod.apply(source, arguments) } event[predicate] = returnFalse }) event.timeStamp || (event.timeStamp = Date.now()) if (source.defaultPrevented !== undefined ? source.defaultPrevented : "returnValue" in source ? source.returnValue === false : source.getPreventDefault && source.getPreventDefault()) event.isDefaultPrevented = returnTrue } return event }
具體來看看他的代碼
if (source || !event.isDefaultPrevented) { source || (source = event)
如果原事件存在,或者事件event的isDefaultPrevented為false或者不存在成立
如果原事件source不存在,就把event賦值給source
$.each(eventMethod, function(name, predicate) { var sourceMethod = source[name] event[name] = function(){ this[predicate] = returnTrue return sourceMethod && sourceMethod.apply(source, arguments) } })
這里是遍歷eventMethod,獲取原事件對應(yīng)的方法名sourceMethod。對event事件進行重新賦值,先把方法賦值為returnTrue函數(shù),返回執(zhí)行原方法的返回值。
event[predicate] = returnFalse
新添加的屬性初始化為returnFalse。
event.timeStamp || (event.timeStamp = Date.now())
看事件是否支持timeStamp,如果不支持,將Date.now()賦值給timeStamp,最后返回做了兼容性處理的event。
createProxyfunction createProxy(event) { var key, proxy = { originalEvent: event } for (key in event) if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key] return compatible(proxy, event) }
這個函數(shù)的作用在于生成代理的event,首先在proxy的originalEvent掛載本身,然后遍歷event,將event的屬性復(fù)制到proxy,最后返回對proxy和event做兼容性處理。
add// element 事件綁定的元素,events綁定的事件列表,fn事件執(zhí)行時的句柄,data傳遞給事件對象的數(shù)據(jù) // 綁定元素的選擇器,delegator事件委托函數(shù),capture哪個階段執(zhí)行事件句柄 function add(element, events, fn, data, selector, delegator, capture){ var id = zid(element), set = (handlers[id] || (handlers[id] = [])) events.split(/s/).forEach(function(event){ if (event == "ready") return $(document).ready(fn) var handler = parse(event) handler.fn = fn handler.sel = selector // emulate mouseenter, mouseleave if (handler.e in hover) fn = function(e){ var related = e.relatedTarget if (!related || (related !== this && !$.contains(this, related))) return handler.fn.apply(this, arguments) } handler.del = delegator var callback = delegator || fn handler.proxy = function(e){ e = compatible(e) if (e.isImmediatePropagationStopped()) return e.data = data var result = callback.apply(element, e._args == undefined ? [e] : [e].concat(e._args)) if (result === false) e.preventDefault(), e.stopPropagation() return result } handler.i = set.length set.push(handler) if ("addEventListener" in element) element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture)) }) }
add方法主要是給元素添加事件和事件響應(yīng)。
id = zid(element), set = (handlers[id] || (handlers[id] = []))
獲取element的id,然后通過id來獲取他的句柄容器
events.split(/s/).forEach(function (event) { if (event == "ready") return $(document).ready(fn) })
對events進行分解,如果event是ready就直接執(zhí)行fn
var handler = parse(event) handler.fn = fn handler.sel = selector
對event進行事件名和命名空間進行分離,然后將信息掛載到handler上,handler的最終結(jié)構(gòu)是這樣的:
{ fn: "", // 函數(shù) e: "", // 事件名 ns: "", // 命名空間 sel: "", // 選擇器 i: "", // 函數(shù)索引 del: "", // 委托函數(shù) proxy: "" // 代理函數(shù) }
繼續(xù)看下面的
if (handler.e in hover) { fn = function (e) { var related = e.relatedTarget; if (!related || (related !== this && !$.contains(this, related))) { return handler.fn.apply(this, arguments) } } }
這就是我們最先提到的mouseover和mouseenter事件,這里就是對Hover事件進行判斷,如果related不存在,或者related不等于目標(biāo)元素,并且不是目標(biāo)元素的子元素,就能夠完成mouseenter的模擬,然后返回函數(shù)處理后的結(jié)果。
handler.proxy = function (e) { e = compatible(e); if (e.isImmediatePropagationStopped()) { return } e.data = data; var result = callback.apply(element, e._args == undefined ? [e] : [e].concat(e._args)) if (result === false) { e.preventDefault(); e.stopPropagation(); } return result; }
首先對e進行兼容處理,然后判斷是否阻止默認(rèn)行為,如果是則啥都不做,把data掛載到event對象上,e是事件執(zhí)行時的event對象,并且使用compatible對e進行修正。調(diào)用句柄,并且返回處理結(jié)果。
set.push(handler) if ("addEventListener" in element) element.addEventListener(realEvent(hander.e), handler.proxy, eventCapture(handler, capture))
向句柄容器添加句柄,并且給元素添加事件。
on$.fn.on = function (event, selector, data, callback, one) { var autoRemove, delegator, $this = this if (event && !isString(event)) { $.each(event, function (type, fn) { $this.on(type, selector, data, fn, one) }) return $this } if (!isString(selector) && !isFunction(callback) && callback !== false) callback = data, data = selector, selector = undefined if (callback === undefined || data === false) callback = data, data = undefined if (callback === false) callback = returnFalse return $this.each(function (_, element) { if (one) autoRemove = function (e) { remove(element, e.type, callback) return callback.apply(this, arguments) } if (selector) delegator = function (e) { var evt, match = $(e.target).closest(selector, element).get(0) if (match && match !== element) { evt = $.extend(createProxy(e), { currentTarget: match, liveFired: element }) return (autoRemove || callback).apply(match, [evt].concat(slice.call(arguments, 1))) } } } } add(element, event, callback, data, selector, delegator || autoRemove) }) }
on方法是給元素綁定事件,最后調(diào)用的add方法。
var autoRemove, delegator, $this = this if (event && !isString(event)) { $.each(event, function (type, fn) { $this.on(type, selector, data, fn, one) }) return $this }
如果event不是字符串,可能就是對象或者數(shù)組,然后對其遍歷,每個都調(diào)用on函數(shù)。
if (!isString(selector) && !isFunction(callback) && callback !== false) callback = data, data = selector, selector = undefined if (callback === undefined || data === false) callback = data, data = undefined
這是針對參數(shù)不同的情況進行的操作
return $this.each(function (_, element) { if (one) autoRemove = function (e) { remove(element, e.type, callback) return callback.apply(this, arguments) } })
如果one為true,autoRemove進行的操作是把元素上對應(yīng)的事件進行解綁,并且調(diào)用回調(diào)。
if (selector) delegator = function (e) { var evt, match = $(e.target).closet(selector, element).get(0) if (match && match !== element) { evt = $.extend(createProxy(e), {currentTarget: match, liveFired: element}) return (autoRemove || callback).apply(match, [evt].concat(slice.call(arguments, 1))) } } add(element, event, callback, data, selector, delegator || autoRemove)
如果selector,就需要做事件代理,調(diào)用closet找到最近selector的元素。如果match存在,并且不是當(dāng)前元素,就調(diào)用createProxy(),給當(dāng)前事件對象創(chuàng)建代理對象,在調(diào)用extend方法,為其擴展currentTarget和liveFired屬性,將代理元素和觸發(fā)元素保存到事件對象中。
最后執(zhí)行句柄函數(shù),match作為上下文,用代理后的event對象evt替換掉原句柄函數(shù)的第一個函數(shù)。
$.fn.triggerHandler = function (event, args) { var e, result; this.each(function(i, element) { e = createProxy(isString(event) ? $.Event(event) : event) e._args = args e.target = element $.each(findHandlers(element, event.type || event), function (i, handler) { result = handler.proxy(e); if (e.isImmediatePropagationStopped()) return false; }) }) return result; }
triggerHandler用于直接執(zhí)行函數(shù)。
this.each(function(i, element) { e = createProxy(isString(event) ? $.Event(event) : event) e._args = args e.target = element
遍歷元素,然后判斷event如果是字符串則使用$.Event創(chuàng)建事件,然后使用createProxy創(chuàng)建代理。
$.each(findHandlers(element, event.type || event), function (i, handler) { result = handler.proxy(e); if (e.isImmediatePropagationStopped()) return false; })
尋找元素上所有的句柄,handler.proxy我們在之前提到過這是真的回調(diào)函數(shù),如果有isImmediatePropagationStopped,則終止遍歷。
Event$.Event = function (type, props) { if (!isString(type)) props = type, type = props.type; var event = document.createEvent(specialEvents[type] || "Events"), bubbles = true if (props) for (var name in props)(name == "bubbles") ? (bubbles = !!props[name]) : (event[name] = props[name]) event.initEvent(type, bubbles, true) return compatible(event) }
簡單來說這個部分就是創(chuàng)建事件,初始化事件,然后返回兼容后的event。
參考文章:
mouseenter與mouseover為何這般糾纏不清?
讀Zepto源碼之Event模塊
Zepto文檔
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/96580.html
摘要:好啦我們已經(jīng)解決了是啥的疑問了,現(xiàn)在回去開始一步步解讀如何實現(xiàn)手動觸發(fā)事件。我們主要看看這里面幾乎含有如何手動觸發(fā)一個事件的大部分步驟和內(nèi)容。 前言 前端在最近幾年實在火爆異常,vue、react、angular各路框架層出不窮,咱們要是不知道個雙向數(shù)據(jù)綁定,不曉得啥是虛擬DOM,也許就被鄙視了。火熱的背后往往也是無盡的浮躁,學(xué)習(xí)這些先進流行的類庫或者框架可以讓我們走的更快,但是靜下心...
摘要:好啦我們已經(jīng)解決了是啥的疑問了,現(xiàn)在回去開始一步步解讀如何實現(xiàn)手動觸發(fā)事件。我們主要看看這里面幾乎含有如何手動觸發(fā)一個事件的大部分步驟和內(nèi)容。 前言 前端在最近幾年實在火爆異常,vue、react、angular各路框架層出不窮,咱們要是不知道個雙向數(shù)據(jù)綁定,不曉得啥是虛擬DOM,也許就被鄙視了。火熱的背后往往也是無盡的浮躁,學(xué)習(xí)這些先進流行的類庫或者框架可以讓我們走的更快,但是靜下心...
摘要:好啦我們已經(jīng)解決了是啥的疑問了,現(xiàn)在回去開始一步步解讀如何實現(xiàn)手動觸發(fā)事件。我們主要看看這里面幾乎含有如何手動觸發(fā)一個事件的大部分步驟和內(nèi)容。 前言 前端在最近幾年實在火爆異常,vue、react、angular各路框架層出不窮,咱們要是不知道個雙向數(shù)據(jù)綁定,不曉得啥是虛擬DOM,也許就被鄙視了。火熱的背后往往也是無盡的浮躁,學(xué)習(xí)這些先進流行的類庫或者框架可以讓我們走的更快,但是靜下心...
摘要:在觸發(fā)事件前,先將保存定時器的變量釋放,如果對象中存在,則觸發(fā)事件,保存的是最后觸摸的時間。如果有觸發(fā)的定時器,清除定時器即可阻止事件的觸發(fā)。其實就是清除所有相關(guān)的定時器,最后將對象設(shè)置為。進入時,立刻清除定時器的執(zhí)行。 大家都知道,因為歷史原因,移動端上的點擊事件會有 300ms 左右的延遲,Zepto 的 touch 模塊解決的就是移動端點擊延遲的問題,同時也提供了滑動的 swip...
摘要:對象待會講,我認(rèn)為是設(shè)計最巧妙的地方。在跨域的時候會使用到,這是為了禁止使用。的目的在于創(chuàng)建一個事件,然后在觸發(fā)他,如果默認(rèn)行為被取消了,則返回。這是的初始化,默認(rèn)是請求,是新建的對象,表示瀏覽器是否應(yīng)該被允許緩存響應(yīng)。 在學(xué)習(xí)zepto的源碼的時候,不得不稱贊這些人的厲害,我雖然能看明白,但是要我寫,估計吭哧吭哧寫不出來。雖然現(xiàn)在很少人使用zepto了,但是學(xué)習(xí)這些源碼我相信每次看都...
閱讀 623·2023-04-26 02:08
閱讀 2654·2021-11-18 10:02
閱讀 3460·2021-11-11 16:55
閱讀 2341·2021-08-17 10:13
閱讀 2901·2019-08-30 15:53
閱讀 685·2019-08-30 15:44
閱讀 2545·2019-08-30 11:10
閱讀 1755·2019-08-29 16:57