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