爲何要看Zepto的源碼,由於公司用的是這個。。。。
再看這個源碼的過程當中,由於對事件類型的不充分,致使學習起來有些費勁,因此在講這個板塊以前先對一些事件進行了解。css
下面是觸發點擊事件的代碼,咱們在inner上添加點擊事件,在wrapper添加事件,點擊inner都會觸發click事件。但這種狀況須要咱們每次都去點擊回調函數纔會執行,有沒有函數不須要咱們手動觸發,自動觸發呢?html
<div class="wrapper"> wrapper <div class="inner">inner</div> </div> <script> var $inner = document.getElementById('inner'); var $wrapper = document.getElementById('wrapper'); $inner.addEventListener('click', function () { console.log(this.innerHTML); }); $wrapper.addEventListener('click', function () { console.log(this.innerHTML); }); </script>
這裏用到了一個須要用到一個API:
createEvent,具體代碼以下:git
let event = document.createEvent('Event') event.initEvent('click', true, true) $inner.dispatchEvent(event)
這裏咱們經過createEvent建立了一個事件,而且其後必須立刻進行初始化,而後經過dispatchEvent進行事件分發,這樣就用js代碼進行事件的觸發,而不須要咱們進行點擊才能觸發。github
在event模塊中有這麼一段代碼api
focus = {focus: 'focusin', blur: 'focusout'}, hover = {mouseenter: 'mouseover', mouseleave: 'mouseout'}
focus和blur咱們都知道,可是爲何要從新隱射focusin和blur事件呢,在mdn中咱們能夠看到focus和focusin的區別在於focus不支持事件冒泡,若是不支持事件冒泡,那麼帶來的效果就是不可以進行事件委託。
一樣的mouseenter和mouseleave也不支持事件冒泡,可是mouseenter會帶來巨大的性能消耗,因此咱們經常使用mouseover進行mouseenter進行事件的模擬。在鼠標事件中,有一個relatedTarget事件,在前面提到由於mouseover支持冒泡,那該如何來模擬mouseenter事件呢。relatedTarget事件屬性返回的是和事件的目標節點相關的節點。對於mouseover事件來講,該屬性是鼠標指針移到目標節點上所離開的那個節點。對於mouseout事件來講,該屬性是離開目標時,鼠標進入的節點。根據上面的描述,咱們能夠對relatedTarget的值進行判斷:若是值不是目標元素,也不是目標元素的子元素,就說明鼠標已經移入目標元素而不是在元素內部移動數組
var _zid = 1 function zid(element) { return element._zid || (element._zid = zid++) }
zid主要是用來標記已經綁定時間的元素,這個函數返回元素的_zid,若是沒有,那就全局的zid加一,而且賦值給元素的_zid屬性瀏覽器
function parse(event) { var parts=('' + event).split('.') return { e: parts[0], ns: parts.slice(1).sort().join(' ') } }
parse方法用來分解事件名和命名空間,{e: 事件名, ns: 命名空間},先把event變成字符串進行分割,獲得事件名,和命名空間,命名空間能夠爲s1.s2.s3這種app
這是用來修正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 }
具體來看看他的代碼post
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,獲取原事件對應的方法名sourceMethod。對event事件進行從新賦值,先把方法賦值爲returnTrue函數,返回執行原方法的返回值。
event[predicate] = returnFalse
新添加的屬性初始化爲returnFalse。
event.timeStamp || (event.timeStamp = Date.now())
看事件是否支持timeStamp,若是不支持,將Date.now()賦值給timeStamp,最後返回作了兼容性處理的event。
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) }
這個函數的做用在於生成代理的event,首先在proxy的originalEvent掛載自己,而後遍歷event,將event的屬性複製到proxy,最後返回對proxy和event作兼容性處理。
// element 事件綁定的元素,events綁定的事件列表,fn事件執行時的句柄,data傳遞給事件對象的數據 // 綁定元素的選擇器,delegator事件委託函數,capture哪一個階段執行事件句柄 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方法主要是給元素添加事件和事件響應。
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就直接執行fn
var handler = parse(event) handler.fn = fn handler.sel = selector
對event進行事件名和命名空間進行分離,而後將信息掛載到handler上,handler的最終結構是這樣的:
{ fn: '', // 函數 e: '', // 事件名 ns: '', // 命名空間 sel: '', // 選擇器 i: '', // 函數索引 del: '', // 委託函數 proxy: '' // 代理函數 }
繼續看下面的
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不等於目標元素,而且不是目標元素的子元素,就可以完成mouseenter的模擬,而後返回函數處理後的結果。
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進行兼容處理,而後判斷是否阻止默認行爲,若是是則啥都不作,把data掛載到event對象上,e是事件執行時的event對象,而且使用compatible對e進行修正。調用句柄,而且返回處理結果。
set.push(handler) if ('addEventListener' in element) element.addEventListener(realEvent(hander.e), handler.proxy, eventCapture(handler, capture))
向句柄容器添加句柄,而且給元素添加事件。
$.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 }
若是event不是字符串,可能就是對象或者數組,而後對其遍歷,每一個都調用on函數。
if (!isString(selector) && !isFunction(callback) && callback !== false) callback = data, data = selector, selector = undefined if (callback === undefined || data === false) callback = data, data = undefined
這是針對參數不一樣的狀況進行的操做
return $this.each(function (_, element) { if (one) autoRemove = function (e) { remove(element, e.type, callback) return callback.apply(this, arguments) } })
若是one爲true,autoRemove進行的操做是把元素上對應的事件進行解綁,而且調用回調。
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,就須要作事件代理,調用closet找到最近selector的元素。若是match存在,而且不是當前元素,就調用createProxy(),給當前事件對象建立代理對象,在調用extend方法,爲其擴展currentTarget和liveFired屬性,將代理元素和觸發元素保存到事件對象中。
最後執行句柄函數,match做爲上下文,用代理後的event對象evt替換掉原句柄函數的第一個函數。
$.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用於直接執行函數。
this.each(function(i, element) { e = createProxy(isString(event) ? $.Event(event) : event) e._args = args e.target = element
遍歷元素,而後判斷event若是是字符串則使用$.Event建立事件,而後使用createProxy建立代理。
$.each(findHandlers(element, event.type || event), function (i, handler) { result = handler.proxy(e); if (e.isImmediatePropagationStopped()) return false; })
尋找元素上全部的句柄,handler.proxy咱們在以前提到過這是真的回調函數,若是有isImmediatePropagationStopped,則終止遍歷。
$.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) }
簡單來講這個部分就是建立事件,初始化事件,而後返回兼容後的event。
參考文章:
mouseenter與mouseover爲什麼這般糾纏不清?
讀Zepto源碼之Event模塊
Zepto文檔