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)