摘要:不支持事件冒泡帶來(lái)的直接后果是不能進(jìn)行事件委托,所以需要對(duì)和事件進(jìn)行模擬。調(diào)用函數(shù),分隔出參數(shù)的事件名和命名空間。這里判斷是否為函數(shù),即第一種傳參方式,調(diào)用函數(shù)的方法,將上下文對(duì)象作為的第一個(gè)參數(shù),如果存在,則與的參數(shù)合并。
Event 模塊是 Zepto 必備的模塊之一,由于對(duì) Event Api 不太熟,Event 對(duì)象也比較復(fù)雜,所以乍一看 Event 模塊的源碼,有點(diǎn)懵,細(xì)看下去,其實(shí)也不太復(fù)雜。
讀Zepto源碼系列文章已經(jīng)放到了github上,歡迎star: reading-zepto
源碼版本本文閱讀的源碼為 zepto1.2.0
準(zhǔn)備知識(shí) focus/blur 的事件模擬為什么要對(duì) focus 和 blur 事件進(jìn)行模擬呢?從 MDN 中可以看到, focus 事件和 blur 事件并不支持事件冒泡。不支持事件冒泡帶來(lái)的直接后果是不能進(jìn)行事件委托,所以需要對(duì) focus 和 blur 事件進(jìn)行模擬。
除了 focus 事件和 blur 事件外,現(xiàn)代瀏覽器還支持 focusin 事件和 focusout 事件,他們和 focus 事件及 blur 事件的最主要區(qū)別是支持事件冒泡。因此可以用 focusin 和模擬 focus 事件的冒泡行為,用 focusout 事件來(lái)模擬 blur 事件的冒泡行為。
我們可以通過(guò)以下代碼來(lái)確定這四個(gè)事件的執(zhí)行順序:
const target = document.getElementById("test") target.addEventListener("focusin", () => {console.log("focusin")}) target.addEventListener("focus", () => {console.log("focus")}) target.addEventListener("blur", () => {console.log("blur")}) target.addEventListener("focusout", () => {console.log("focusout")})
在 chrome59下, input 聚焦和失焦時(shí),控制臺(tái)會(huì)打印出如下結(jié)果:
"focus" "focusin" "blur" "focusout"
可以看到,在此瀏覽器中,事件的執(zhí)行順序應(yīng)該是 focus > focusin > blur > focusout
關(guān)于這幾個(gè)事件更詳細(xì)的描述,可以查看:《說(shuō)說(shuō)focus /focusin /focusout /blur 事件》
關(guān)于事件的執(zhí)行順序,我測(cè)試的結(jié)果與文章所說(shuō)的有點(diǎn)不太一樣。感興趣的可以點(diǎn)擊這個(gè)鏈接測(cè)試下http://jsbin.com/nizugazamo/edit?html,js,console,output。不過(guò)我覺(jué)得執(zhí)行順序可以不必細(xì)究,可以將 focusin 作為 focus 事件的冒泡版本。
mouseenter/mouseleave 的事件模擬跟 focus 和 blur 一樣,mouseenter 和 mouseleave 也不支持事件的冒泡, 但是 mouseover 和 mouseout 支持事件冒泡,因此,這兩個(gè)事件的冒泡處理也可以分別用 mouseover 和 mouseout 來(lái)模擬。
在鼠標(biāo)事件的 event 對(duì)象中,有一個(gè) relatedTarget 的屬性,從 MDN:MouseEvent.relatedTarget 文檔中,可以看到,mouseover 的 relatedTarget 指向的是移到目標(biāo)節(jié)點(diǎn)上時(shí)所離開(kāi)的節(jié)點(diǎn)( exited from ),mouseout 的 relatedTarget 所指向的是離開(kāi)所在的節(jié)點(diǎn)后所進(jìn)入的節(jié)點(diǎn)( entered to )。
另外 mouseover 事件會(huì)隨著鼠標(biāo)的移動(dòng)不斷觸發(fā),但是 mouseenter 事件只會(huì)在進(jìn)入節(jié)點(diǎn)的那一刻觸發(fā)一次。如果鼠標(biāo)已經(jīng)在目標(biāo)節(jié)點(diǎn)上,那 mouseover 事件觸發(fā)時(shí)的 relatedTarget 為當(dāng)前節(jié)點(diǎn)。
因此,要模擬 mouseenter 或 mouseleave 事件,只需要確定觸發(fā) mouseover 或 mouseout 事件上的 relatedTarget 不存在,或者 relatedTarget 不為當(dāng)前節(jié)點(diǎn),并且不為當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn),避免子節(jié)點(diǎn)事件冒泡的影響。
關(guān)于 mouseenter 和 mouseleave 的模擬, 謙龍 有篇文章《mouseenter與mouseover為何這般糾纏不清?》寫(xiě)得很清楚,建議讀一下。
Event 模塊的核心將 Event 模塊簡(jiǎn)化后如下:
;(function($){})(Zepto)
其實(shí)就是向閉包中傳入 Zepto 對(duì)象,然后對(duì) Zepto 對(duì)象做一些擴(kuò)展。
在 Event 模塊中,主要做了如下幾件事:
提供簡(jiǎn)潔的API
統(tǒng)一不同瀏覽器的 event 對(duì)象
事件句柄緩存池,方便手動(dòng)觸發(fā)事件和解綁事件。
事件委托
內(nèi)部方法 zidvar _zid = 1 function zid(element) { return element._zid || (element._zid = _zid++) }
獲取參數(shù) element 對(duì)象的 _zid 屬性,如果屬性不存在,則全局變量 _zid 增加 1 ,作為 element 的 _zid 的屬性值返回。這個(gè)方法用來(lái)標(biāo)記已經(jīng)綁定過(guò)事件的元素,方便查找。
parsefunction parse(event) { var parts = ("" + event).split(".") return {e: parts[0], ns: parts.slice(1).sort().join(" ")} }
在 zepto 中,支持事件的命名空間,可以用 eventType.ns1.ns2... 的形式來(lái)給事件添加一個(gè)或多個(gè)命名空間。
parse 函數(shù)用來(lái)分解事件名和命名空間。
"" + event 是將 event 變成字符串,再以 . 分割成數(shù)組。
返回的對(duì)象中,e 為事件名, ns 為排序后,以空格相連的命名空間字符串,形如 ns1 ns2 ns3 ... 的形式。
matcherForfunction matcherFor(ns) { return new RegExp("(?:^| )" + ns.replace(" ", " .* ?") + "(?: |$)") }
生成匹配命名空間的表達(dá)式,例如,傳進(jìn)來(lái)的參數(shù) ns 為 ns1 ns2 ns3 ,最終生成的正則為 /(?:^| )ns1.* ?ns2.* ?ns3(?: |$)/。至于有什么用,下面馬上講到。
findHandlers,查找緩存的句柄handlers = {} function findHandlers(element, event, fn, selector) { event = parse(event) if (event.ns) var matcher = matcherFor(event.ns) return (handlers[zid(element)] || []).filter(function(handler) { return handler && (!event.e || handler.e == event.e) && (!event.ns || matcher.test(handler.ns)) && (!fn || zid(handler.fn) === zid(fn)) && (!selector || handler.sel == selector) }) }
查找元素對(duì)應(yīng)的事件句柄。
event = parse(event)
調(diào)用 parse 函數(shù),分隔出 event 參數(shù)的事件名和命名空間。
if (event.ns) var matcher = matcherFor(event.ns)
如果命名空間存在,則生成匹配該命名空間的正則表達(dá)式 matcher。
return (handlers[zid(element)] || []).filter(function(handler) { ... })
返回的其實(shí)是 handlers[zid(element)] 中符合條件的句柄函數(shù)。 handlers 是緩存的句柄容器,用 element 的 _zid 屬性值作為 key 。
return handler // 條件1 && (!event.e || handler.e == event.e) // 條件2 && (!event.ns || matcher.test(handler.ns)) // 條件3 && (!fn || zid(handler.fn) === zid(fn)) // 條件4 && (!selector || handler.sel == selector) // 條件5
返回的句柄必須滿足5個(gè)條件:
句柄必須存在
如果 event.e 存在,則句柄的事件名必須與 event 的事件名一致
如果命名空間存在,則句柄的命名空間必須要與事件的命名空間匹配( matcherFor 的作用 )
如果指定匹配的事件句柄為 fn ,則當(dāng)前句柄 handler 的 _zid 必須與指定的句柄 fn 相一致
如果指定選擇器 selector ,則當(dāng)前句柄中的選擇器必須與指定的選擇器一致
從上面的比較可以看到,緩存的句柄對(duì)象的形式如下:
{ fn: "", // 函數(shù) e: "", // 事件名 ns: "", // 命名空間 sel: "", // 選擇器 // 除此之外,其實(shí)還有 i: "", // 函數(shù)索引 del: "", // 委托函數(shù) proxy: "", // 代理函數(shù) // 后面這幾個(gè)屬性會(huì)講到 }realEvent,返回對(duì)應(yīng)的冒泡事件
focusinSupported = "onfocusin" in window, focus = { focus: "focusin", blur: "focusout" }, hover = { mouseenter: "mouseover", mouseleave: "mouseout" } function realEvent(type) { return hover[type] || (focusinSupported && focus[type]) || type }
這個(gè)函數(shù)其實(shí)是將 focus/blur 轉(zhuǎn)換成 focusin/focusout ,將 mouseenter/mouseleave 轉(zhuǎn)換成 mouseover/mouseout 事件。
由于 focusin/focusout 事件瀏覽器支持程度還不是很好,因此要對(duì)瀏覽器支持做一個(gè)檢測(cè),如果瀏覽器支持,則返回,否則,返回原事件名。
compatible,修正event對(duì)象returnTrue = function(){return true}, returnFalse = function(){return false}, 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 }) try { event.timeStamp || (event.timeStamp = Date.now()) } catch (ignored) { } if (source.defaultPrevented !== undefined ? source.defaultPrevented : "returnValue" in source ? source.returnValue === false : source.getPreventDefault && source.getPreventDefault()) event.isDefaultPrevented = returnTrue } return event }
compatible 函數(shù)用來(lái)修正 event 對(duì)象的瀏覽器差異,向 event 對(duì)象中添加了 isDefaultPrevented、isImmediatePropagationStopped、isPropagationStopped 幾個(gè)方法,對(duì)不支持 timeStamp 的瀏覽器,向 event 對(duì)象中添加 timeStamp 屬性。
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 })
判斷條件是,原事件對(duì)象存在,或者事件 event 的 isDefaultPrevented 不存在時(shí)成立。
如果 source 不存在,則將 event 賦值給 source, 作為原事件對(duì)象。
遍歷 eventMethods ,獲得原事件對(duì)象的對(duì)應(yīng)方法名 sourceMethod。
event[name] = function(){ this[predicate] = returnTrue return sourceMethod && sourceMethod.apply(source, arguments) }
改寫(xiě) event 對(duì)象相應(yīng)的方法,如果執(zhí)行對(duì)應(yīng)的方法時(shí),先將事件中方法所對(duì)應(yīng)的新方法賦值為 returnTrue 函數(shù) ,例如執(zhí)行 preventDefault 方法時(shí), isDefaultPrevented 方法的返回值為 true。
event[predicate] = returnFalse
這是將新添加的屬性,初始化為 returnFalse 方法
try { event.timeStamp || (event.timeStamp = Date.now()) } catch (ignored) { }
這段向不支持 timeStamp 屬性的瀏覽器中添加 timeStamp 屬性。
if (source.defaultPrevented !== undefined ? source.defaultPrevented : "returnValue" in source ? source.returnValue === false : source.getPreventDefault && source.getPreventDefault()) event.isDefaultPrevented = returnTrue }
這是對(duì)瀏覽器 preventDefault 不同實(shí)現(xiàn)的兼容。
source.defaultPrevented !== undefined ? source.defaultPrevented : "三元表達(dá)式"
如果瀏覽器支持 defaultPrevented, 則返回 defaultPrevented 的值
"returnValue" in source ? source.returnValue === false : "后一個(gè)判斷"
returnValue 默認(rèn)為 true,如果阻止了瀏覽器的默認(rèn)行為, returnValue 會(huì)變?yōu)?false 。
source.getPreventDefault && source.getPreventDefault()
如果瀏覽器支持 getPreventDefault 方法,則調(diào)用 getPreventDefault() 方法獲取是否阻止瀏覽器的默認(rèn)行為。
判斷為 true 的時(shí)候,將 isDefaultPrevented 設(shè)置為 returnTrue 方法。
createProxy,創(chuàng)建代理對(duì)象ignoreProperties = /^([A-Z]|returnValue$|layer[XY]$|webkitMovement[XY]$)/, function 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) }
zepto 中,事件觸發(fā)的時(shí)候,返回給我們的 event 都不是原生的 event 對(duì)象,都是代理對(duì)象,這個(gè)就是代理對(duì)象的創(chuàng)建方法。
ignoreProperties 用來(lái)排除 A-Z 開(kāi)頭,即所有大寫(xiě)字母開(kāi)頭的屬性,還有以returnValue 結(jié)尾,layerX/layerY ,webkitMovementX/webkitMovementY 結(jié)尾的非標(biāo)準(zhǔn)屬性。
for (key in event) if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key]
遍歷原生事件對(duì)象,排除掉不需要的屬性和值為 undefined 的屬性,將屬性和值復(fù)制到代理對(duì)象上。
最終返回的是修正后的代理對(duì)象
eventCapturefunction eventCapture(handler, captureSetting) { return handler.del && (!focusinSupported && (handler.e in focus)) || !!captureSetting }
返回 true 表示在捕獲階段執(zhí)行事件句柄,否則在冒泡階段執(zhí)行。
如果存在事件代理,并且事件為 focus/blur 事件,在瀏覽器不支持 focusin/focusout 事件時(shí),設(shè)置為 true , 在捕獲階段處理事件,間接達(dá)到冒泡的目的。
否則作用自定義的 captureSetting 設(shè)置事件執(zhí)行的時(shí)機(jī)。
add,Event 模塊的核心方法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),參數(shù)比較多,先來(lái)看看各參數(shù)的含義:
element // 事件綁定的元素 events // 需要綁定的事件列表 fn // 事件執(zhí)行時(shí)的句柄 data // 事件執(zhí)行時(shí),傳遞給事件對(duì)象的數(shù)據(jù) selector // 事件綁定元素的選擇器 delegator // 事件委托函數(shù) capture // 那個(gè)階段執(zhí)行事件句柄
var id = zid(element), set = (handlers[id] || (handlers[id] = []))
獲取或設(shè)置 id , set 為事件句柄容器。
events.split(/s/).forEach(function(event){})
對(duì)每個(gè)事件進(jìn)行處理
if (event == "ready") return $(document).ready(fn)
如果為 ready 事件,則調(diào)用 ready 方法,中止后續(xù)的執(zhí)行
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
這段代碼是設(shè)置 handler 上的一些屬性,緩存起來(lái)。
這里主要看對(duì) mouseenter 和 mouseleave 事件的模擬,具體的原理上面已經(jīng)說(shuō)過(guò),只有在條件成立的時(shí)候才會(huì)執(zhí)行事件句柄。
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 }
事件句柄的代理函數(shù)。
e 為事件執(zhí)行時(shí)的原生 event 對(duì)象,因此先調(diào)用 compatible 對(duì) e 進(jìn)行修正。
調(diào)用 isImmediatePropagationStopped 方法,看是否已經(jīng)執(zhí)行過(guò) stopImmediatePropagation 方法,如果已經(jīng)執(zhí)行,則中止后續(xù)程序的執(zhí)行。
再擴(kuò)展 e 對(duì)象,將 data 存到 e 的 data 屬性上。
執(zhí)行事件句柄,將 e 對(duì)象作為句柄的第一個(gè)參數(shù)。
如果執(zhí)行完畢后,顯式返回 false,則阻止瀏覽器的默認(rèn)行為和事件冒泡。
set.push(handler) if ("addEventListener" in element) element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
將句柄存入句柄容器
調(diào)用元素的 addEventListener 方法,添加事件,事件的回調(diào)函數(shù)用的是句柄的代理函數(shù),eventCapture(handler, capture) 來(lái)用指定是否在捕獲階段執(zhí)行。
remove,刪除事件function remove(element, events, fn, selector, capture){ var id = zid(element) ;(events || "").split(/s/).forEach(function(event){ findHandlers(element, event, fn, selector).forEach(function(handler){ delete handlers[id][handler.i] if ("removeEventListener" in element) element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture)) }) }) }
首先獲取指定元素的 _zid
;(events || "").split(/s/).forEach(function(event){})
遍歷需要?jiǎng)h除的 events
findHandlers(element, event, fn, selector).forEach(function(handler){})
調(diào)用 findHandlers 方法,查找 event 下需要?jiǎng)h除的事件句柄
delete handlers[id][handler.i]
刪除句柄容器中對(duì)應(yīng)的事件,在 add 函數(shù)中的句柄對(duì)象中的 i 屬性就用在這里了,方便查找需要?jiǎng)h除的句柄。
element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
調(diào)用 removeEventListener 方法,刪除對(duì)應(yīng)的事件。
工具函數(shù) $.event$.event = { add: add, remove: remove }
將 add 方法和 remove 方法暴露出去,應(yīng)該是方便第三方插件做擴(kuò)展
$.proxy$.proxy = function(fn, context) { var args = (2 in arguments) && slice.call(arguments, 2) if (isFunction(fn)) { var proxyFn = function(){ return fn.apply(context, args ? args.concat(slice.call(arguments)) : arguments) } proxyFn._zid = zid(fn) return proxyFn } else if (isString(context)) { if (args) { args.unshift(fn[context], fn) return $.proxy.apply(null, args) } else { return $.proxy(fn[context], fn) } } else { throw new TypeError("expected function") } }
代理函數(shù),作用有點(diǎn)像 JS 中的 bind 方法,返回的是一個(gè)代理后改變執(zhí)行上下文的函數(shù)。
var args = (2 in arguments) && slice.call(arguments, 2)
如果提供超過(guò)3個(gè)參數(shù),則去除前兩個(gè)參數(shù),將后面的參數(shù)作為執(zhí)行函數(shù) fn 的參數(shù)。
if (isFunction(fn)) { var proxyFn = function(){ return fn.apply(context, args ? args.concat(slice.call(arguments)) : arguments) } proxyFn._zid = zid(fn) return proxyFn }
proxy 的執(zhí)行函數(shù)有兩種傳遞方式,一是在第一個(gè)參數(shù)直接傳入,二是第一個(gè)參數(shù)為上下文對(duì)象,執(zhí)行函數(shù)也在上下文對(duì)象中一起傳入。
這里判斷 fn 是否為函數(shù),即第一種傳參方式,調(diào)用 fn 函數(shù)的 apply 方法,將上下文對(duì)象 context 作為 apply 的第一個(gè)參數(shù),如果 args 存在,則與 fn 的參數(shù)合并。
給代理后的函數(shù)加上 _zid 屬性,方便函數(shù)的查找。
else if (isString(context)) { if (args) { args.unshift(fn[context], fn) return $.proxy.apply(null, args) } else { return $.proxy(fn[context], fn) }
如果函數(shù)已經(jīng)包含在上下文對(duì)象中,即第一個(gè)參數(shù) fn 為對(duì)象,第二個(gè)參數(shù) context 為字符串,用來(lái)指定執(zhí)行函數(shù)的在上下文對(duì)象中的屬性名。
if (args) { args.unshift(fn[context], fn) return $.proxy.apply(null, args) }
如果參數(shù)存在時(shí),將 fn[context] ,也即執(zhí)行函數(shù)和 fn ,也即上下文對(duì)象放入 args 數(shù)組的開(kāi)頭,這樣就將參數(shù)修正成跟第一種傳參方式一樣,再調(diào)用 $.proxy 函數(shù)。這里調(diào)用 apply 方法,是因?yàn)椴恢绤?shù)有多少個(gè),調(diào)用 apply 可以以數(shù)組的形式傳入。
如果 args 不存在時(shí),確定的參數(shù)項(xiàng)只有兩個(gè),因此可以直接調(diào)用 $.proxy 方法。
$.EventspecialEvents={}, specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = "MouseEvents" $.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) }
specialEvents 是將鼠標(biāo)事件修正為 MouseEvents ,這應(yīng)該是處理瀏覽器的兼容問(wèn)題,可能有些瀏覽器中,這些事件的事件類型并不是 MouseEvents 。
$.Event 方法用來(lái)手動(dòng)創(chuàng)建特定類型的事件。
參數(shù) type 可以為字符串,也可以為 event 對(duì)象。props 為擴(kuò)展 event 對(duì)象的對(duì)象。
if (!isString(type)) props = type, type = props.type
如果不是字符串,也即是 event 對(duì)象時(shí),將 type 賦給 props ,type 為當(dāng)前 event 對(duì)象中的 type 屬性值。
var event = document.createEvent(specialEvents[type] || "Events"), bubbles = true
調(diào)用 createEvent 方法,創(chuàng)建對(duì)應(yīng)類型的 event 事件,并將事件冒泡默認(rèn)設(shè)置為 true
if (props) for (var name in props) (name == "bubbles") ? (bubbles = !!props[name]) : (event[name] = props[name])
遍歷 props 屬性,如果有指定 bubbles ,則采用指定的冒泡行為,其他屬性復(fù)制到 event 對(duì)象上,實(shí)現(xiàn)對(duì) event 對(duì)象的擴(kuò)展。
event.initEvent(type, bubbles, true) return compatible(event)
初始化新創(chuàng)建的事件,并將修正后的事件對(duì)象返回。
方法 .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 方法來(lái)用給元素綁定事件,最終調(diào)用的是 add 方法,前面的一大段邏輯主要是修正參數(shù)。
var autoRemove, delegator, $this = this if (event && !isString(event)) { $.each(event, function(type, fn){ $this.on(type, selector, data, fn, one) }) return $this }
autoRemove 表示在執(zhí)行完事件響應(yīng)后,自動(dòng)解綁的函數(shù)。
event 可以為字符串或者對(duì)象,當(dāng)為對(duì)象時(shí),對(duì)象的屬性為事件類型,屬性值為句柄。
這段是處理 event 為對(duì)象時(shí)的情況,遍歷對(duì)象,得到事件類型和句柄,然后再次調(diào)用 on 方法,繼續(xù)修正后續(xù)的參數(shù)。
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
先來(lái)分析第一個(gè) if ,selector 不為 string ,callback 不為函數(shù),并且 callback 不為 false 時(shí)的情況。
這里可以確定 selector 并沒(méi)有傳遞,因?yàn)?selector 不是必傳的參數(shù)。
因此這里將 data 賦給 callback,selector 賦給 data ,將 selector 設(shè)置為 undefined ,因?yàn)?selector 沒(méi)有傳遞,因此相應(yīng)參數(shù)的位置都前移了一位。
再來(lái)看第二個(gè) if ,如果 callback( 原來(lái)的 data ) 為 undefined , data 為 false 時(shí),表示 selector 沒(méi)有傳遞,并且 data 也沒(méi)有傳遞,因此將 data 賦給 callback ,將 data 設(shè)置為 undefined ,即將參數(shù)再前移一位。
第三個(gè) if ,如果 callback === false ,用 returnFalse 函數(shù)代替,如果不用 returnFalse 代替,會(huì)報(bào)錯(cuò)。
return $this.each(function(_, element){ add(element, event, callback, data, selector, delegator || autoRemove) })
可以看到,這里是遍歷元素集合,為每個(gè)元素都調(diào)用 add 方法,綁定事件。
if (one) autoRemove = function(e){ remove(element, e.type, callback) return callback.apply(this, arguments) }
如果只調(diào)用一次,設(shè)置 autoRemove 為一個(gè)函數(shù),這個(gè)函數(shù)在句柄執(zhí)行前,調(diào)用 remove 方法,將綁定在元素上對(duì)應(yīng)事件解綁。
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))) } }
如果 selector 存在,表示需要做事件代理。
調(diào)用 closest 方法,從事件的目標(biāo)元素 e.target 開(kāi)始向上查找,返回第一個(gè)匹配 selector 的元素。關(guān)于 closest 方法,見(jiàn)《讀Zepto源碼之集合元素查找》分析。
如果 match 存在,并且 match 不為當(dāng)前元素,則調(diào)用 createProxy 方法,為當(dāng)前事件對(duì)象創(chuàng)建代理對(duì)象,再調(diào)用 $.extend 方法,為代理對(duì)象擴(kuò)展 currentTarget 和 liveFired 屬性,將代理元素和觸發(fā)事件的元素保存到事件對(duì)象中。
最后執(zhí)行句柄函數(shù),以代理元素 match 作為句柄的上下文,用代理后的 event 對(duì)象 evt 替換掉原句柄函數(shù)的第一個(gè)參數(shù)。
將該函數(shù)賦給 delegator ,作為代理函數(shù)傳遞給 add 方法。
.off()$.fn.off = function(event, selector, callback){ var $this = this if (event && !isString(event)) { $.each(event, function(type, fn){ $this.off(type, selector, fn) }) return $this } if (!isString(selector) && !isFunction(callback) && callback !== false) callback = selector, selector = undefined if (callback === false) callback = returnFalse return $this.each(function(){ remove(this, event, callback, selector) }) }
解綁事件
if (event && !isString(event)) { $.each(event, function(type, fn){ $this.off(type, selector, fn) }) return $this }
這段邏輯與 on 方法中的相似,修正參數(shù),不再細(xì)說(shuō)。
if (!isString(selector) && !isFunction(callback) && callback !== false) callback = selector, selector = undefined if (callback === false) callback = returnFalse
第一個(gè) if 是處理 selector 參數(shù)沒(méi)有傳遞的情況的, selector 位置傳遞的其實(shí)是 callback 。
第二個(gè) if 是判斷如果 callback 為 false ,將 callback 賦值為 returnFalse 函數(shù)。
return $this.each(function(){ remove(this, event, callback, selector) })
最后遍歷所有元素,調(diào)用 remove 函數(shù),為每個(gè)元素解綁事件。
.bind()$.fn.bind = function(event, data, callback){ return this.on(event, data, callback) }
bind 方法內(nèi)部調(diào)用的其實(shí)是 on 方法。
.unbind()$.fn.unbind = function(event, callback){ return this.off(event, callback) }
unbind 方法內(nèi)部調(diào)用的是 off 方法。
.one()$.fn.one = function(event, selector, data, callback){ return this.on(event, selector, data, callback, 1) }
one 方法內(nèi)部調(diào)用的也是 on 方法,只不過(guò)默認(rèn)傳遞了 one 參數(shù)為 1 ,表示綁定的事件只執(zhí)行一下。
.delegate()$.fn.delegate = function(selector, event, callback){ return this.on(event, selector, callback) }
事件委托,也是調(diào)用 on 方法,只是 selector 一定要傳遞。
.undelegate()$.fn.undelegate = function(selector, event, callback){ return this.off(event, selector, callback) }
取消事件委托,內(nèi)部調(diào)用的是 off 方法,selector 必須要傳遞。
.live()$.fn.live = function(event, callback){ $(document.body).delegate(this.selector, event, callback) return this }
動(dòng)態(tài)創(chuàng)建的節(jié)點(diǎn)也可以響應(yīng)事件。其實(shí)事件綁定在 body 上,然后委托到當(dāng)前節(jié)點(diǎn)上。內(nèi)部調(diào)用的是 delegate 方法。
.die()$.fn.die = function(event, callback){ $(document.body).undelegate(this.selector, event, callback) return this }
將由 live 綁定在 body 上的事件銷毀,內(nèi)部調(diào)用的是 undelegate 方法。
.triggerHandler()$.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 }
直接觸發(fā)事件回調(diào)函數(shù)。
參數(shù) event 可以為事件類型字符串,也可以為 event 對(duì)象。
e = createProxy(isString(event) ? $.Event(event) : event)
如果 event 為字符串時(shí),則調(diào)用 $.Event 工具函數(shù)來(lái)初始化一個(gè)事件對(duì)象,再調(diào)用 createProxy 來(lái)創(chuàng)建一個(gè) event 代理對(duì)象。
$.each(findHandlers(element, event.type || event), function(i, handler){ result = handler.proxy(e) if (e.isImmediatePropagationStopped()) return false })
調(diào)用 findHandlers 方法來(lái)找出事件的所有句柄,調(diào)用 proxy 方法,即真正綁定到事件上的回調(diào)函數(shù)(參見(jiàn) add 的解釋),拿到方法返回的結(jié)果 result ,并查看 isImmediatePropagationStopped 返回的結(jié)果是否為 true ,如果是,立刻中止后續(xù)執(zhí)行。
如果返回的結(jié)果 result 為 false ,也立刻中止后續(xù)執(zhí)行。
由于 triggerHandler 直接觸發(fā)回調(diào)函數(shù),所以事件不會(huì)冒泡。
.trigger()$.fn.trigger = function(event, args){ event = (isString(event) || $.isPlainObject(event)) ? $.Event(event) : compatible(event) event._args = args return this.each(function(){ // handle focus(), blur() by calling them directly if (event.type in focus && typeof this[event.type] == "function") this[event.type]() // items in the collection might not be DOM elements else if ("dispatchEvent" in this) this.dispatchEvent(event) else $(this).triggerHandler(event, args) }) }
手動(dòng)觸發(fā)事件。
event = (isString(event) || $.isPlainObject(event)) ? $.Event(event) : compatible(event)
event 可以傳遞事件類型,對(duì)象和 event 對(duì)象。
如果傳遞的是字符串或者純粹對(duì)象,則先調(diào)用 $.Event 方法來(lái)初始化事件,否則調(diào)用 compatible 方法來(lái)修正 event 對(duì)象,由于 $.Event 方法在內(nèi)部其實(shí)已經(jīng)調(diào)用過(guò) compatible 方法修正 event 對(duì)象了的,所以外部不需要再調(diào)用一次。
if (event.type in focus && typeof this[event.type] == "function") this[event.type]()
如果是 focus/blur 方法,則直接調(diào)用 this.focus() 或 this.blur() 方法,這兩個(gè)方法是瀏覽器原生支持的。
如果 this 為 DOM 元素,即存在 dispatchEvent 方法,則用 dispatchEvent 來(lái)觸發(fā)事件,關(guān)于 dispatchEvent ,可以參考 MDN: EventTarget.dispatchEvent()。
否則,直接調(diào)用 triggerHandler 方法來(lái)觸發(fā)事件的回調(diào)函數(shù)。
由于 trigger 是通過(guò)觸發(fā)事件來(lái)執(zhí)行事件句柄的,因此事件會(huì)冒泡。
系列文章讀Zepto源碼之代碼結(jié)構(gòu)
讀 Zepto 源碼之內(nèi)部方法
讀Zepto源碼之工具函數(shù)
讀Zepto源碼之神奇的$
讀Zepto源碼之集合操作
讀Zepto源碼之集合元素查找
讀Zepto源碼之操作DOM
讀Zepto源碼之樣式操作
讀Zepto源碼之屬性操作
參考mouseenter與mouseover為何這般糾纏不清?
向zepto.js學(xué)習(xí)如何手動(dòng)(trigger)觸發(fā)DOM事件
誰(shuí)說(shuō)你只是 "會(huì)用"jQuery?
Zepto源碼分析-event模塊
zepto源碼之event.js
說(shuō)說(shuō)focus /focusin /focusout /blur 事件
MDN:mouseenter
MDN:mouseleave
MDN:MouseEvent.relatedTarget
MDN:Event reference
MDN:Document.createEvent()
MDN:EventTarget.dispatchEvent()
MDN:event.stopImmediatePropagation
License最后,所有文章都會(huì)同步發(fā)送到微信公眾號(hào)上,歡迎關(guān)注,歡迎提意見(jiàn):
作者:對(duì)角另一面
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/84090.html
摘要:模塊處理的是表單提交。表單提交包含兩部分,一部分是格式化表單數(shù)據(jù),另一部分是觸發(fā)事件,提交表單。最終返回的結(jié)果是一個(gè)數(shù)組,每個(gè)數(shù)組項(xiàng)為包含和屬性的對(duì)象。否則手動(dòng)綁定事件,如果沒(méi)有阻止瀏覽器的默認(rèn)事件,則在第一個(gè)表單上觸發(fā),提交表單。 Form 模塊處理的是表單提交。表單提交包含兩部分,一部分是格式化表單數(shù)據(jù),另一部分是觸發(fā) submit 事件,提交表單。 讀 Zepto 源碼系列文章已...
摘要:在觸發(fā)事件前,先將保存定時(shí)器的變量釋放,如果對(duì)象中存在,則觸發(fā)事件,保存的是最后觸摸的時(shí)間。如果有觸發(fā)的定時(shí)器,清除定時(shí)器即可阻止事件的觸發(fā)。其實(shí)就是清除所有相關(guān)的定時(shí)器,最后將對(duì)象設(shè)置為。進(jìn)入時(shí),立刻清除定時(shí)器的執(zhí)行。 大家都知道,因?yàn)闅v史原因,移動(dòng)端上的點(diǎn)擊事件會(huì)有 300ms 左右的延遲,Zepto 的 touch 模塊解決的就是移動(dòng)端點(diǎn)擊延遲的問(wèn)題,同時(shí)也提供了滑動(dòng)的 swip...
摘要:讀源碼系列文章已經(jīng)放到了上,歡迎源碼版本本文閱讀的源碼為改寫(xiě)原有的方法模塊改寫(xiě)了以上這些方法,這些方法在調(diào)用的時(shí)候,會(huì)為返回的結(jié)果添加的屬性,用來(lái)保存原來(lái)的集合。方法的分析可以看讀源碼之模塊。 Stack 模塊為 Zepto 添加了 addSelf 和 end 方法。 讀 Zepto 源碼系列文章已經(jīng)放到了github上,歡迎star: reading-zepto 源碼版本 本文閱讀的...
摘要:模塊是為解決移動(dòng)版加載圖片過(guò)大過(guò)多時(shí)崩潰的問(wèn)題。因?yàn)闆](méi)有處理過(guò)這樣的場(chǎng)景,所以這部分的代碼解釋不會(huì)太多,為了說(shuō)明這個(gè)問(wèn)題,我翻譯了這篇文章作為附文怎樣處理移動(dòng)端對(duì)圖片資源的限制,更詳細(xì)地解釋了這個(gè)模塊的應(yīng)用場(chǎng)景。 assets 模塊是為解決 Safari 移動(dòng)版加載圖片過(guò)大過(guò)多時(shí)崩潰的問(wèn)題。因?yàn)闆](méi)有處理過(guò)這樣的場(chǎng)景,所以這部分的代碼解釋不會(huì)太多,為了說(shuō)明這個(gè)問(wèn)題,我翻譯了《How to...
摘要:模塊基于上的事件的封裝,利用屬性,封裝出系列事件。這個(gè)判斷需要引入設(shè)備偵測(cè)模塊。然后是監(jiān)測(cè)事件,根據(jù)這三個(gè)事件,可以組合出和事件。其中變量對(duì)象和模塊中的對(duì)象的作用差不多,可以先看看讀源碼之模塊對(duì)模塊的分析。 Gesture 模塊基于 IOS 上的 Gesture 事件的封裝,利用 scale 屬性,封裝出 pinch 系列事件。 讀 Zepto 源碼系列文章已經(jīng)放到了github上,歡...
閱讀 2985·2021-10-19 11:46
閱讀 979·2021-08-03 14:03
閱讀 2934·2021-06-11 18:08
閱讀 2905·2019-08-29 13:52
閱讀 2744·2019-08-29 12:49
閱讀 480·2019-08-26 13:56
閱讀 924·2019-08-26 13:41
閱讀 849·2019-08-26 13:35