源碼註釋jquery
// Zepto.js // (c) 2010-2015 Thomas Fuchs // Zepto.js may be freely distributed under the MIT license. ;(function($){ var _zid = 1, undefined, slice = Array.prototype.slice, isFunction = $.isFunction, isString = function(obj){ return typeof obj == 'string' }, handlers = {},//_zid: events 事件緩存池 specialEvents={}, focusinSupported = 'onfocusin' in window, //是否支持即將獲取焦點時觸發函數 onfocusin focus不支持冒泡 focus = { focus: 'focusin', blur: 'focusout' }, //焦點修正 hover = { mouseenter: 'mouseover', mouseleave: 'mouseout' } // mouseenter mouseleave不冒泡的修正 ,mouseover mouseout功能同樣且支持冒泡 //此處標準瀏覽器,click、mousedown、mouseup、mousemove拋出的就是MouseEvents,應該也是對低版本IE等某些瀏覽器的修正 specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents' /** * 取元素標識符,沒有設置一個返回 * @param element * @returns {*|number} */ function zid(element) { return element._zid || (element._zid = _zid++) } /** * 查找元素上事件響應函數集合 * @param element * @param event * @param fn * @param selector * @returns {Array} */ 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)) //判斷事件命名空間是否相同 RegExp.prototype.test = function(String) {}; && (!fn || zid(handler.fn) === zid(fn)) // zid(handler.fn)返回handler.fn的標識,沒有加一個,判斷fn標識符是否相同 && (!selector || handler.sel == selector) //返回 handler, 判斷selector是否相同 }) } /** * 解析事件類型 * @param event 'click' * @returns {{e: * 事件類型 , ns: string 命名空間}} */ function parse(event) { //若是有.分隔,證實有命名空間 var parts = ('' + event).split('.') return {e: parts[0], ns: parts.slice(1).sort().join(' ')} } /** * 生成命名空間的正則對象 * @param ns * @returns {RegExp} */ function matcherFor(ns) { return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)') } /** * 事件捕獲 * 對focus和blur事件且瀏覽器不支持focusin focusout,經過設置捕獲來模擬冒泡 * @param handler * @param captureSetting * @returns {*|boolean|boolean} */ function eventCapture(handler, captureSetting) { //若是是focus和blur事件且瀏覽器不支持focusin focusout時, //設置爲可捕獲,間接達到冒泡的目的 return handler.del && (!focusinSupported && (handler.e in focus)) || !!captureSetting } /** * 修正事件類型 focus->focusIn blur->focusOut mouseenter->mouseover mouseleave->mouseout * @param type 事件類型 * @returns {*|boolean|*|*} */ function realEvent(type) { //hover[type] mouseenter和mouseleave 轉換成 mouseover和mouseout // focus[type] focus blur 修正爲 focusin focusout return hover[type] || (focusinSupported && focus[type]) || type } /** * 增長事件底層方法 * @param element * @param events 字符串 如‘click' * @param fn * @param data * @param selector * @param delegator * @param capture */ function add(element, events, fn, data, selector, delegator, capture){ //zid Zepto會在elemnt上擴展一個標識屬性_zid // 讀取元素上已綁定的事件處理函數 var id = zid(element), set = (handlers[id] || (handlers[id] = [])) // \s 匹配空格 events.split(/\s/).forEach(function(event){ //若是是ready事件 if (event == 'ready') return $(document).ready(fn) //解析事件 {e: * 事件類型 , ns: string 命名空間} var handler = parse(event) //保存fn,下面爲了處理mouseenter, mouseleave時,對fn進行了修改 //存儲fn響應函數 //存儲selector handler.fn = fn handler.sel = selector // emulate mouseenter, mouseleave // 模仿 mouseenter, mouseleave //若是事件是mouseenter, mouseleave,模擬mouseover mouseout事件處理 if (handler.e in hover) fn = function(e){ // relatedTarget 事件屬性返回與事件的目標節點相關的節點。 // 對於 mouseover 事件來講,該屬性是鼠標指針移到目標節點上時所離開的那個節點。 // 對於 mouseout 事件來講,該屬性是離開目標時,鼠標指針進入的節點。 // 對於其餘類型的事件來講,這個屬性沒有用。 var related = e.relatedTarget //不存在,代表不是mouseover、mouseout事件, //related !== this && !$.contains(this, related)) 當related不在事件對象event內 表示事件已觸發完成,不是在move過程當中,須要執行響應函數 if (!related || (related !== this && !$.contains(this, related))) //執行響應函數 return handler.fn.apply(this, arguments) } //事件委託 handler.del = delegator var callback = delegator || fn handler.proxy = function(e){ //修正event e = compatible(e) //若是是阻止全部事件觸發 if (e.isImmediatePropagationStopped()) return e.data = data //緩存數據 //執行回調函數,context:element,arguments:event,e._args(默認是undefind,trigger()時傳遞的參數) var result = callback.apply(element, e._args == undefined ? [e] : [e].concat(e._args)) //當事件響應函數返回false時,阻止瀏覽器默認操做和冒泡 if (result === false) e.preventDefault(), e.stopPropagation() return result } //設置事件響應函數的索引,刪除事件時,根據它來刪除 delete handlers[id][handler.i] handler.i = set.length //緩存到handlers[id]裏 set = handlers[id] set.push(handler) //元素支持DOM2級事件綁定 if ('addEventListener' in element) //綁定事件 //DOM源碼 // @param {string} type // @param {EventListener|Function} listener // @param {boolean} [useCapture] 是否使用捕捉,默認 false // EventTarget.prototype.addEventListener = function(type,listener,useCapture) {}; //realEvent(handler.e) 修正後的事件類型 //handler.proxy 修正爲代理上下文的事件響應函數 // eventCapture(handler, capture) element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture)) }) } /** * 刪除事件, 對應add * @param element * @param events * @param fn * @param selector * @param capture 是否捕獲 */ function remove(element, events, fn, selector, capture){ var id = zid(element) //找到元素標識 ;(events || '').split(/\s/).forEach(function(event){ //events多個以空格分隔 //遍歷事件響應函數集合 findHandlers(element, event, fn, selector).forEach(function(handler){ delete handlers[id][handler.i] //刪除緩存在handlers的響應函數 if ('removeEventListener' in element) //調用DOM原生方法刪除事件 //DOM源代碼 // /** // @param {string} type // @param {EventListener|Function} listener // @param {boolean} [useCapture] // */ // EventTarget.prototype.removeEventListener = function(type,listener,useCapture) {}; //realEvent(handler.e) 修正事件類型 handler.proxy 代理的事件響應函數 eventCapture(handler, capture)修正的是否捕獲 //與增長事件底層函數 add最後一行 element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture)) 呼應 element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture)) }) }) } //此處不清楚要幹嗎,將事件兩個核心底層方法封裝到event對象裏,方便作Zepto插件事件擴展? $.event = { add: add, remove: remove } /** * 代理 * (function,context),(context,name) * @param fn * @param context * @returns {*} */ $.proxy = function(fn, context) { var args = (2 in arguments) && slice.call(arguments, 2) //若是傳了第3個參數,取到第3個參數之後(包含第3個參數)全部的參數數組,挺好的判斷技巧 if (isFunction(fn)) { //fn是函數 //採用閉包,以context調用函數。 // args.concat(slice.call(arguments)) 將傳參挪到前面 如傳遞給$.proxy(fn,context,3,4); 轉變成 fn.apply(context,[3,4,fn,context,3,4]) var proxyFn = function(){ return fn.apply(context, args ? args.concat(slice.call(arguments)) : arguments) } // 標記函數 proxyFn._zid = zid(fn) return proxyFn } else if (isString(context)) { //context是字符串, 實際傳參(context,name) if (args) { //修正傳參,再以$.proxy調用 args.unshift(fn[context], fn) // unshift 往數組開頭添加新的項 return $.proxy.apply(null, args) } else { return $.proxy(fn[context], fn) } } else { throw new TypeError("expected function") //拋出異常:要求的函數類型錯誤 } } /** * 綁定事件,應直接採用on * 源自1.9版本前jquery的綁定事件的區分: bind()是直接綁定在元素上 .live()則是經過冒泡的方式來綁定到元素上的。更適合列表類型的,綁定到document DOM節點上。和.bind()的優點是支持動態數據。 .delegate()則是更精確的小範圍使用事件代理,性能優於.live() .on()則是1.9版本整合了以前的三種方式的新事件綁定機制 * @param event * @param data * @param callback * @returns {*} */ $.fn.bind = function(event, data, callback){ return this.on(event, data, callback) } /** * 解綁事件,應直接用off * @param event * @param callback * @returns {*} */ $.fn.unbind = function(event, callback){ return this.off(event, callback) } /** * 綁定一次性事件 * @param event * @param selector * @param data * @param callback * @returns {*} */ $.fn.one = function(event, selector, data, callback){ return this.on(event, selector, data, callback, 1) } var returnTrue = function(){return true}, returnFalse = function(){return false}, ignoreProperties = /^([A-Z]|returnValue$|layer[XY]$)/,//匹配 大寫字母A-Z開頭/returnValue/layerX/layerY用於createProxy(),過濾event對象的屬性 eventMethods = { preventDefault: 'isDefaultPrevented',//是否已調用preventDefault() preventDefault 阻止瀏覽器的默認動做 stopImmediatePropagation: 'isImmediatePropagationStopped', //是否已調用stopImmediatePropagation(),stopImmediatePropagation DOM3提出的阻止任何事件觸發 stopPropagation: 'isPropagationStopped' //是否已調用stopPropagation() stopPropagation阻止冒泡 } /** * 修正event對象 * @param event 代理的event對象 原生event對象 * @param source 原生event對象 * @returns {*} */ function compatible(event, source) { //event.isDefaultPrevented 是否已調用了preventDefault方法 // //event是代理事件對象時,賦值給source if (source || !event.isDefaultPrevented) { source || (source = event) //遍歷,代理preventDefault stopImmediatePropagation stopPropagation等方法 $.each(eventMethods, function(name, predicate) { var sourceMethod = source[name] event[name] = function(){ //擴展event對象,代理preventDefault stopImmediatePropagation stopPropagation方法 ,兼容瀏覽器不支持,同時作其餘事情 this[predicate] = returnTrue //若是執行了3方法,原生事件對象isDefaultPrevented isImmediatePropagationStopped isPropagationStopped 三方法標記true return sourceMethod && sourceMethod.apply(source, arguments) //且調用原生方法 } event[predicate] = returnFalse //擴展原生事件對象 isDefaultPrevented isImmediatePropagationStopped isPropagationStopped三方法,默認返回false。 }) //若是瀏覽器支持 defaultPrevented DOM3 EVENT提出的可否取消默認行爲 if (source.defaultPrevented !== undefined ? source.defaultPrevented : 'returnValue' in source ? source.returnValue === false : source.getPreventDefault && source.getPreventDefault()) event.isDefaultPrevented = returnTrue //默承認以取消 } //返回修正對象 return event } /** * 建立事件代理 * @param event Event對象 * @returns {*} */ function createProxy(event) { var key, proxy = { originalEvent: event } //存儲原始event for (key in event) //複製event屬性至proxy,ignoreProperties裏包含的屬性除外 if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key] // return compatible(proxy, event) } /** * 小範圍冒泡綁定事件,應直接採用on */ $.fn.delegate = function(selector, event, callback){ return this.on(event, selector, callback) } /** * 解綁事件,應直接用off */ $.fn.undelegate = function(selector, event, callback){ return this.off(event, selector, callback) } /** * 冒泡到document.body綁定事件,應直接採用on * @param event * @param callback * @returns {*} */ $.fn.live = function(event, callback){ $(document.body).delegate(this.selector, event, callback) return this } /** * 在doument.body解綁事件,應直接用off */ $.fn.die = function(event, callback){ $(document.body).undelegate(this.selector, event, callback) return this } /** * 擴展Zepto on監聽事件方法 * 元素上綁定一個或多個事件的事件處理函數 * 注意: 方法參數不該超過5個,超過5個,應該用arguments。5個是慣例。if或for或閉包嵌套層也不該超過5層 * @param event 事件集 字符串/ * @param selector 子選擇器 * @param data event.data * @param callback 事件響應函數 * @param one 內部用, $.fn.one用。標記一次性事件 * @returns {*} */ $.fn.on = function(event, selector, data, callback, one){ var autoRemove, delegator, $this = this //event是對象{click:fn},支持這種方式我以爲沒多大用 if (event && !isString(event)) { $.each(event, function(type, fn){ $this.on(type, selector, data, fn, one) }) return $this } //選擇器非字符串 callback非方法 //未傳data on('click','.ss',function(){}) if (!isString(selector) && !isFunction(callback) && callback !== false) callback = data, data = selector, selector = undefined //data傳了function 或未傳 if (callback === undefined || data === false) callback = data, data = undefined //callback傳了false,轉換成false函數 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){ //以element元素爲容器,以事件源爲起點,往上冒泡找到匹配selector的元素 // match 響應函數對應的事件源 var evt, match = $(e.target).closest(selector, element).get(0) // selector能找到,且不是容器,即不是綁定事件的上下文,即$('.parent').on('click','.son',fn)形式。開始處理委託。 if (match && match !== element) { //createProxy(e) 建立event代理對象 currentTarget指向selector元素,liveFired指向綁定事件的容器element evt = $.extend(createProxy(e), {currentTarget: match, liveFired: element}) //執行事件響應函數 //autoRemove觸發一次事件響應函數後自動銷燬。 callback觸發事件響應函數 // [evt].concat(slice.call(arguments, 1))響應函數的參數數組 return (autoRemove || callback).apply(match, [evt].concat(slice.call(arguments, 1))) } } add(element, event, callback, data, selector, delegator || autoRemove) }) } /** * 移除事件響應函數 * @param event * @param selector * @param callback * @returns {*} */ $.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) }) } /** * 觸發事件 * @param event 事件類型 * @param args * @returns {*} */ $.fn.trigger = function(event, args){ //修正event爲事件對象 event = (isString(event) || $.isPlainObject(event)) ? $.Event(event) : compatible(event) //傳參 event._args = args return this.each(function(){ // handle focus(), blur() by calling them directly //若是事件是focus blur if (event.type in focus && typeof this[event.type] == "function") this[event.type]() // items in the collection might not be DOM elements // 支持瀏覽器原生觸發事件API //DOM源碼 // /** // @param {Event} event // @return {boolean} // */ // EventTarget.prototype.dispatchEvent = function(event) {}; else if ('dispatchEvent' in this) this.dispatchEvent(event) //模擬觸發事件 else $(this).triggerHandler(event, args) }) } // triggers event handlers on current element just as if an event occurred, // doesn't trigger an actual event, doesn't bubble /** * 觸發事件,不能冒泡 * @param event event對象 * @param args 傳參 * @returns {*} */ $.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){ //調用 handler.proxy執行事件 result = handler.proxy(e) //若是event調用了immediatePropagationStopped(),終止後續事件的響應 if (e.isImmediatePropagationStopped()) return false }) }) return result } // shortcut methods for `.bind(event, fn)` for each event type /** * 給經常使用事件生成便捷方法 * @param event * @param args * @returns {*} */ ;('focusin focusout focus blur load resize scroll unload click dblclick '+ 'mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave '+ 'change select keydown keypress keyup error').split(' ').forEach(function(event) { $.fn[event] = function(callback) { return (0 in arguments) ? //有callback回調,是綁定事件,不然,觸發事件 , // 不用on?on才通用啊 ,bind也是調用on //$.fn.bind = function(event, data, callback){ // return this.on(event, data, callback) // } this.bind(event, callback) : this.trigger(event) } }) /** * 建立Event對象 * @param type * @param props 擴展到Event對象上的屬性 * @returns {*} * @constructor */ $.Event = function(type, props) { //當type是個對象時 if (!isString(type)) props = type, type = props.type //對應 specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents' //建立event對象,若是是click,mousedown,mouseup mousemove,建立爲MouseEvent對象,bubbles設爲冒泡 //TODO: 爲何要把這些事件單獨拎出來? var event = document.createEvent(specialEvents[type] || 'Events'), bubbles = true // (name == 'bubbles') ? (bubbles = !!props[name])若是是冒泡,確保是true/false 瀏覽器只識別true、false, !!props[name]明確進行類型轉換 // event[name] = props[name] props屬性擴展到event對象上 if (props) for (var name in props) (name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name]) //初始化event對象,type爲事件類型,如click,bubbles爲是否冒泡,第三個參數表示是否能夠用preventDefault方法來取消默認操做 //初始化event對象,type:事件類型 如click bubbles可否 true: 可否使用preventDefault取消瀏覽器默認操做 //附上DOM源碼 /* @browser Gecko @param {string} eventTypeArg @param {boolean} canBubbleArg @param {boolean} cancelableArg */ //Event.prototype.initEvent = function(eventTypeArg,canBubbleArg,cancelableArg) {}; event.initEvent(type, bubbles, true) //添加isDefaultPrevented方法,event.defaultPrevented返回一個布爾值,代表當前事件的默認動做是否被取消,也就是是否執行了 event.preventDefault()方法. return compatible(event) } })(Zepto)
三大核心方法on/off/trigger流程解析數組
瀏覽器原生支持自定義事件舉例(Zepto自定義事件/瀏覽器事件原理)瀏覽器
var element = document.body; //綁定事件元素 var handler = function(e,a,b){ //事件響應函數 alert('監聽成功 a:' +a+' b:'+b +' e.data:'+ JSON.stringify(e.data)); } //監聽事件 element.addEventListener('add',function(e){ e.data = {wo:1}; //對監聽事件進行傳參 handler.apply(element, e._args == undefined ? [e] : [e].concat(e._args)); },false); //拋出自定義事件 var addEvent = document.createEvent('Events'); addEvent.initEvent('add',true,true); addEvent._args = [2,3]; //傳參 element.dispatchEvent(addEvent);
方法圖 緩存