1 // Zepto.js 2 // (c) 2010-2016 Thomas Fuchs 3 // Zepto.js may be freely distributed under the MIT license. 4 5 ;(function($){ 6 //_zid:element的唯一标识 7 var _zid = 1, undefined, 8 slice = Array.prototype.slice, 9 isFunction = $.isFunction, 10 isString = function(obj){ return typeof obj == 'string' }, 11 handlers = {}, 12 specialEvents={}, 13 //focusin focusout不是所有浏览器都支持的。谷歌支持,IE从10开始支持,火狐一直不支持 14 //https://developer.mozilla.org/en-US/docs/Web/Events 此处可以看出focus blur不支持冒泡.在支持的浏览器中,顺序如下focus>>focusin>>blur>>focusout 15 focusinSupported = 'onfocusin' in window, 16 focus = { focus: 'focusin', blur: 'focusout' }, 17 //https://developer.mozilla.org/en-US/docs/Web/Events/mouseenter mouseenter不冒泡 mouseover冒泡。这就会导致父节点监听mouseover事件,其子节点有mouseover时,父节点一回收到mouseover事件 18 hover = { mouseenter: 'mouseover', mouseleave: 'mouseout' } 19 20 specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents' //一般事件用Events 21 22 function zid(element) { 23 return element._zid || (element._zid = _zid++) 24 } 25 26 /* 27 查找一个预算绑定的函数 事件类型相同,命名空间相同 选择器相同 28 输入节点,事件类型 29 输出处理函数 30 */ 31 function findHandlers(element, event, fn, selector) { 32 event = parse(event) 33 if (event.ns) var matcher = matcherFor(event.ns) 34 return (handlers[zid(element)] || []).filter(function(handler) { 35 return handler 36 && (!event.e || handler.e == event.e) 37 && (!event.ns || matcher.test(handler.ns)) 38 && (!fn || zid(handler.fn) === zid(fn)) 39 && (!selector || handler.sel == selector) 40 }) 41 } 42 43 //解析事件类型、命名空间 44 function parse(event) { 45 var parts = ('' + event).split('.') 46 return {e: parts[0], ns: parts.slice(1).sort().join(' ')} 47 } 48 function matcherFor(ns) { 49 return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)') 50 } 51 52 //对于不冒泡的事件要用捕获的方法达到冒泡的目的 53 function eventCapture(handler, captureSetting) { 54 return handler.del && 55 (!focusinSupported && (handler.e in focus)) || 56 !!captureSetting 57 } 58 59 //将不可冒泡的事件用可冒泡的事件代替 60 function realEvent(type) { 61 return hover[type] || (focusinSupported && focus[type]) || type 62 } 63 64 //核心函数 65 function add(element, events, fn, data, selector, delegator, capture){ 66 //set是该节点绑定的监听函数集合 67 var id = zid(element), set = (handlers[id] || (handlers[id] = [])) 68 events.split(/s/).forEach(function(event){ 69 if (event == 'ready') return $(document).ready(fn) 70 var handler = parse(event) 71 handler.fn = fn 72 handler.sel = selector 73 // emulate mouseenter, mouseleave 74 75 if (handler.e in hover) fn = function(e){ 76 /* 77 event有:target:事件发生的节点 currentTarget:监听器所在的节点 relatedTarget:只对mouseover mouseenter mouseout一类事件有效。进入一个节点,被进入的是target,从哪进入的是relatedTarget 78 下面的判断的意思是:若不是内部移动,则执行函数。可以避免收到多次mouseenter、mouseleave事件 79 */ 80 var related = e.relatedTarget 81 if (!related || (related !== this && !$.contains(this, related))) 82 return handler.fn.apply(this, arguments) 83 } 84 handler.del = delegator 85 var callback = delegator || fn 86 //事件处理函数 加一层代理 方便删除,并加入preventDefault() stopPropagation() 87 handler.proxy = function(e){ 88 e = compatible(e) 89 if (e.isImmediatePropagationStopped()) return //若事件某一步已执行了stopImmediatePropagationStop(),则回调函数不再执行(从源节点到各个接收到冒泡事件的父节点都是同一个事件) 90 e.data = data 91 var result = callback.apply(element, e._args == undefined ? [e] : [e].concat(e._args)) 92 if (result === false) e.preventDefault(), e.stopPropagation() //回调函数 返回false,则不再冒泡或执行默认事件 93 return result 94 } 95 handler.i = set.length 96 set.push(handler) 97 if ('addEventListener' in element) 98 element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture)) //eventCapture保证绑定在父节点上的监听函数可以收到事件,避免不冒泡的事件导致父节点监听不到的情况 99 }) 100 } 101 function remove(element, events, fn, selector, capture){ 102 var id = zid(element) 103 ;(events || '').split(/s/).forEach(function(event){ 104 findHandlers(element, event, fn, selector).forEach(function(handler){ 105 delete handlers[id][handler.i] 106 if ('removeEventListener' in element) 107 element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture)) 108 }) 109 }) 110 } 111 112 $.event = { add: add, remove: remove } 113 114 $.proxy = function(fn, context) { 115 var args = (2 in arguments) && slice.call(arguments, 2) 116 if (isFunction(fn)) { 117 var proxyFn = function(){ return fn.apply(context, args ? args.concat(slice.call(arguments)) : arguments) } 118 proxyFn._zid = zid(fn) 119 return proxyFn 120 } else if (isString(context)) { 121 if (args) { 122 args.unshift(fn[context], fn) 123 return $.proxy.apply(null, args) 124 } else { 125 return $.proxy(fn[context], fn) 126 } 127 } else { 128 throw new TypeError("expected function") 129 } 130 } 131 132 $.fn.bind = function(event, data, callback){ 133 return this.on(event, data, callback) 134 } 135 $.fn.unbind = function(event, callback){ 136 return this.off(event, callback) 137 } 138 $.fn.one = function(event, selector, data, callback){ 139 return this.on(event, selector, data, callback, 1) 140 } 141 142 var returnTrue = function(){return true}, 143 returnFalse = function(){return false}, 144 ignoreProperties = /^([A-Z]|returnValue$|layer[XY]$|webkitMovement[XY]$)/, 145 eventMethods = { 146 preventDefault: 'isDefaultPrevented', 147 stopImmediatePropagation: 'isImmediatePropagationStopped', 148 stopPropagation: 'isPropagationStopped' 149 } 150 151 function compatible(event, source) { 152 if (source || !event.isDefaultPrevented) { 153 source || (source = event) 154 155 $.each(eventMethods, function(name, predicate) { 156 var sourceMethod = source[name] 157 event[name] = function(){ 158 this[predicate] = returnTrue 159 return sourceMethod && sourceMethod.apply(source, arguments) 160 } 161 event[predicate] = returnFalse 162 }) 163 164 event.timeStamp || (event.timeStamp = Date.now()) 165 166 if (source.defaultPrevented !== undefined ? source.defaultPrevented : 167 'returnValue' in source ? source.returnValue === false : 168 source.getPreventDefault && source.getPreventDefault()) 169 event.isDefaultPrevented = returnTrue 170 } 171 return event 172 } 173 174 function createProxy(event) { 175 var key, proxy = { originalEvent: event } 176 for (key in event) 177 if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key] 178 179 return compatible(proxy, event) 180 } 181 182 $.fn.delegate = function(selector, event, callback){ 183 return this.on(event, selector, callback) 184 } 185 $.fn.undelegate = function(selector, event, callback){ 186 return this.off(event, selector, callback) 187 } 188 189 $.fn.live = function(event, callback){ 190 $(document.body).delegate(this.selector, event, callback) 191 return this 192 } 193 $.fn.die = function(event, callback){ 194 $(document.body).undelegate(this.selector, event, callback) 195 return this 196 } 197 198 $.fn.on = function(event, selector, data, callback, one){ 199 var autoRemove, delegator, $this = this 200 if (event && !isString(event)) { //处理批量绑定事件的情况 201 $.each(event, function(type, fn){ 202 $this.on(type, selector, data, fn, one) 203 }) 204 return $this 205 } 206 207 /* 208 处理绑定的各种情况,最全的是5个参数,最简单的$el.bind('click', callback) 209 */ 210 if (!isString(selector) && !isFunction(callback) && callback !== false) 211 callback = data, data = selector, selector = undefined //这是只有三个参数的情况,1、event-type 2、data 3、callback。类似$el.bind('click', data, callback) 212 if (callback === undefined || data === false) 213 callback = data, data = undefined //若第三个参数还是未定义,则认为是最简单的情况$el.bind('click', callback) 214 215 if (callback === false) callback = returnFalse //若第二个参数仍未定义,则认为是$el.bind('click') 216 217 return $this.each(function(_, element){ 218 if (one) autoRemove = function(e){ //若one有效值,这个在第一遍调用后移除监听事件 219 remove(element, e.type, callback) 220 return callback.apply(this, arguments) 221 } 222 223 //若绑定事件使用了选择器,则使用代理函数 224 if (selector) delegator = function(e){ 225 //closest是一个很重要的方法 与parents类似,但只会返回最近的节点 226 var evt, match = $(e.target).closest(selector, element).get(0) 227 //此处 接收到event后 若检测不匹配 则代理函数为空 228 if (match && match !== element) { 229 evt = $.extend(createProxy(e), {currentTarget: match, liveFired: element}) 230 return (autoRemove || callback).apply(match, [evt].concat(slice.call(arguments, 1))) 231 } 232 } 233 234 add(element, event, callback, data, selector, delegator || autoRemove) 235 }) 236 } 237 $.fn.off = function(event, selector, callback){ 238 var $this = this 239 if (event && !isString(event)) { 240 $.each(event, function(type, fn){ 241 $this.off(type, selector, fn) 242 }) 243 return $this 244 } 245 246 if (!isString(selector) && !isFunction(callback) && callback !== false) 247 callback = selector, selector = undefined 248 249 if (callback === false) callback = returnFalse 250 251 return $this.each(function(){ 252 remove(this, event, callback, selector) 253 }) 254 } 255 256 //一个相对独立的函数,提供给外部使用 257 $.fn.trigger = function(event, args){ 258 event = (isString(event) || $.isPlainObject(event)) ? $.Event(event) : compatible(event) 259 event._args = args 260 return this.each(function(){ 261 // handle focus(), blur() by calling them directly 262 if (event.type in focus && typeof this[event.type] == "function") this[event.type]() 263 // items in the collection might not be DOM elements 264 else if ('dispatchEvent' in this) this.dispatchEvent(event) //创建一个事件,dispatchEvent一下触发事件,就是这么简单 265 else $(this).triggerHandler(event, args) 266 }) 267 } 268 269 // triggers event handlers on current element just as if an event occurred, 270 // doesn't trigger an actual event, doesn't bubble 271 $.fn.triggerHandler = function(event, args){ 272 var e, result 273 this.each(function(i, element){ 274 e = createProxy(isString(event) ? $.Event(event) : event) 275 e._args = args 276 e.target = element 277 $.each(findHandlers(element, event.type || event), function(i, handler){ 278 result = handler.proxy(e) 279 if (e.isImmediatePropagationStopped()) return false 280 }) 281 }) 282 return result 283 } 284 285 // shortcut methods for `.bind(event, fn)` for each event type 286 ;('focusin focusout focus blur load resize scroll unload click dblclick '+ 287 'mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave '+ 288 'change select keydown keypress keyup error').split(' ').forEach(function(event) { 289 $.fn[event] = function(callback) { 290 return (0 in arguments) ? 291 this.bind(event, callback) : 292 this.trigger(event) //向外部提供$.click(callback())这种函数调用方式。另外,参数为空就触发对应事件 293 } 294 }) 295 296 //创建事件的基本方法 MouseEvents || Events 297 $.Event = function(type, props) { 298 if (!isString(type)) props = type, type = props.type 299 var event = document.createEvent(specialEvents[type] || 'Events'), bubbles = true 300 if (props) for (var name in props) (name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name]) 301 event.initEvent(type, bubbles, true) 302 return compatible(event) 303 } 304 305 })(Zepto)