之前说完$(XXX),然后还有很多零零碎碎的东西需要去分析,结果一看代码,发现zepto的实现都相对简单,没有太多可分析的。直接略过了一些实现,直接研究Event模块,相比JQuery的事件系统,zepto的设计相对简单很多,看起来也就相对轻松,整个event模块也就300行代码。
先看事件的相关接口以及用法
$.Event
$.Event(type, [properties]) ⇒ event
创建并初始化一个指定的DOM事件。如果给定properties对象,使用它来扩展出新的事件对象。默认情况下,事件被设置为冒泡方式;这个可以通过设置bubbles
为false
来关闭。
一个事件初始化的函数可以使用trigger来触发。
$.Event('mylib:change', { bubbles: false })
$.proxy v1.0+
$.proxy(fn, context) ⇒ function
$.proxy(fn, context, [additionalArguments...]) ⇒ function v1.1.4+
$.proxy(context, property) ⇒ function
$.proxy(context, property, [additionalArguments...]) ⇒ function v1.1.4+
接受一个函数,然后返回一个新函数,并且这个新函数始终保持了特定的上下文(context)语境,新函数中this
指向context参数。另外一种形式,原始的function是从上下文(context)对象的特定属性读取。
如果传递超过2个的额外参数,它们被用于 传递给fn参数的函数 引用。
bind
不推荐, 使用on代替。
delegate
不推荐, 使用on代替。
die
不推荐, 使用on代替。
live
不推荐, 使用on代替。
unbind
不推荐
undelegate
不推荐
off
off(type, [selector], function(e){ ... }) ⇒ self
off({ type: handler, type2: handler2, ... }, [selector]) ⇒ self
off(type, [selector]) ⇒ self
off() ⇒ self
移除通过on添加的事件.移除一个特定的事件处理程序, 必须通过用on()
添加的那个相同的函数。否则,只通过事件类型调用此方法将移除该类型的所有处理程序。如果没有参数,将移出当前元素上全部的注册事件。
on
on(type, [selector], function(e){ ... }) ⇒ self
on(type, [selector], [data], function(e){ ... }) ⇒ self v1.1+
on({ type: handler, type2: handler2, ... }, [selector]) ⇒ self
on({ type: handler, type2: handler2, ... }, [selector], [data]) ⇒ self v1.1+
添加事件处理程序到对象集合中得元素上。多个事件可以通过空格的字符串方式添加,或者以事件类型为键、以函数为值的对象 方式。如果给定css选择器,当事件在匹配该选择器的元素上发起时,事件才会被触发(即事件委派,或者说事件代理)。
如果给定data
参数,这个值将在事件处理程序执行期间被作为有用的 event.data
属性
事件处理程序在添加该处理程序的元素、或在给定选择器情况下匹配该选择器的元素的上下文中执行(this指向触发事件的元素)。 当一个事件处理程序返回false
,preventDefault()
和 stopPropagation()
被当前事件调用的情况下, 将防止默认浏览器操作,如链接,同时执行阻止事件冒泡。
one
添加一个处理事件到元素,当第一次执行事件以后,该事件将自动解除绑定,保证处理函数在每个元素上最多执行一次,使用方法和on相同
event.isDefaultPrevented v1.1+
event.isDefaultPrevented() ⇒ boolean
如果preventDefault()
被该事件的实例调用,那么返回true。 这可作为跨平台的替代原生的 defaultPrevented
属性,如果 defaultPrevented
缺失或在某些浏览器下不可靠的时候。
event.isImmediatePropagationStopped v1.1+
event.isImmediatePropagationStopped() ⇒ boolean
如果stopImmediatePropagation()
被该事件的实例调用,那么返回true。Zepto在不支持该原生方法的浏览器中实现它, (例如老版本的Android)。
event.isPropagationStopped v1.1+
event.isPropagationStopped() ⇒ boolean
如果stopPropagation()
被该事件的实例调用,那么返回true。
trigger
trigger(event, [args]) ⇒ self
在对象集合的元素上触发指定的事件。事件可以是一个字符串类型,也可以是一个 通过$.Event 定义的事件对象。如果给定args参数,它会作为参数传递给事件函数
triggerHandler
triggerHandler(event, [args]) ⇒ self
像trigger它只在当前元素上触发事件,但不冒泡。 在代码注释里面有详细解释
以上均是说明事件api的使用,接下来分析一些具体的实现。先从最常用的on开始说起,先上代码,看看zepto里面on的具体实现。
$.fn.on = function(event, selector, data, callback, one) {
var autoRemove, delegator, $this = this
//如果是{'click':function(){},'touchmove':function(){}}
//此时event是Object
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 (isFunction(data) || 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)
}
//事件委托,这里是事件冒泡到element元素上
if (selector) delegator = function(e) {
//事件触发元素e.target的祖先级元素
var evt, match = $(e.target).closest(selector, element).get(0)
//找到了 并且不是element本身
if (match && match !== element) {
//创建一个event对象
evt = $.extend(createProxy(e), {
currentTarget: match,//匹配到的元素
liveFired: element//委托的元素
})
//(autoRemove || callback)不是一次性事件,就调用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)
})
}
看上面代码就知道,在on里面对一次性事件,委托事件都做了相关处理,也对$(XXX).on({'click':function(){},'touchmove':function(){}})这种传递Object的情况做了相应处理。
一次性事件,及时绑定函数只值执行一次,zepto里面简单处理,执行一次就移除该事件绑定,所以最后把我们传入的回调包装了一下
//如果是一次性事件
if (one) autoRemove = function(e) {
//移除该事件
remove(element, e.type, callback)
//执行回调
return callback.apply(this, arguments)
}
如果是委托事件,也是把我们传入的callback进行了包装
if (selector) delegator = function(e) {
//事件触发元素e.target的祖先级元素
var evt, match = $(e.target).closest(selector, element).get(0)
//找到了 并且不是element本身
if (match && match !== element) {
//创建一个event对象
evt = $.extend(createProxy(e), {
currentTarget: match,//匹配到的元素
liveFired: element//委托的元素
})
//(autoRemove || callback)不是一次性事件,就调用callback,
// [evt].concat(slice.call(arguments, 1))拼接参数数组。
return (autoRemove || callback).apply(match, [evt].concat(slice.call(arguments, 1)))
}
}
不管是一次性事件,还是事件委托,在on里面只是对我们的callback做了一些包装处理,然后再调用add方法执行最后的绑定,看看add的实现
function add(element, events, fn, data, selector, delegator, capture) {
//取到元素的zid
var id = zid(element),
set = (handlers[id] || (handlers[id] = [])) //元素上已经绑定的所有事件处理函数,如果没有赋值一个新数组 events.split(/\s/).forEach(function(event) {
//如果是绑定dom ready事件
if (event == 'ready') return $(document).ready(fn)
//解析事件类型,返回一个包含事件名称和事件命名空间的对象
var handler = parse(event)
// //保存fn,下面为了处理mouseenter, mouseleave时,对fn进行了修改
handler.fn = fn
handler.sel = selector
// emulate mouseenter, mouseleave
// 模仿 mouseenter, mouseleave
if (handler.e in hover) fn = function(e) {
/*
relatedTarget为事件相关对象,只有在mouseover和mouseout事件时才有值
mouseover时表示的是鼠标移出的那个对象,mouseout时表示的是鼠标移入的那个对象
当related不存在,表示事件不是mouseover或者mouseout,mouseover时!$.contains(this, related)当相关对象不在事件对象内
且related !== this相关对象不是事件对象时,表示鼠标已经从事件对象外部移入到了对象本身,这个时间是要执行处理函数的
当鼠标从事件对象上移入到子节点的时候related就等于this了,且!$.contains(this, related)也不成立,这个时间是不需要执行处理函数的
*/
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)
//这个event对象执行过阻止冒泡方法stopImmediatePropagation,这里直接返回。
if (e.isImmediatePropagationStopped()) return
e.data = data
//调用之前传入的回调函数
var result = callback.apply(element, e._args == undefined ? [e] : [e].concat(e._args))
//当事件处理函数返回false时,阻止默认操作和冒泡
if (result === false) e.preventDefault(), e.stopPropagation()
return result
}
//设置处理函数的在函数集中的位置,remove的时候要用到
handler.i = set.length
//将函数存入函数集中,引用类型,你懂的,handlers里面也有了
set.push(handler)
if ('addEventListener' in element)
//realEvent(handler.e) 处理事件类型,eventCapture绑定事件类型,是捕获还是冒泡
element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
})
}
里面zid,负责到元素上的一个属性,_zid的值初始从1开始。
//取element的唯一标示符,如果没有,则设置一个并返回 ,保证-zid的唯一性
function zid(element) {
return element._zid || (element._zid = _zid++)
}
如果是同时绑定多个事件$(XXX).on('tap click',XXX); 通过events.split(/\s/).forEach进行处理
var handler = parse(event),主要是解析出时间的命名空间,以及绑定事件的类型
function parse(event) {
var parts = ('' + event).split('.')
return {
e: parts[0],
//name space
ns: parts.slice(1).sort().join(' ')
}
}
最终指向绑定
if ('addEventListener' in element)
//realEvent(handler.e) 处理事件类型,eventCapture绑定事件类型,是捕获还是冒泡
element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
可以看到绑定的时间处理函数是handler.proxy,这个其实也是把传入的callback进行了再次包装。不难想象,如果我们绑定的是一次性事件或者是委托事件、先是在on里面进行了一次包装,然后到了add里面再做了一次,每次的包装都是有特殊意义的。要完全明白这些东西,还是脱离不了基础!! 闭包、作用域、函数、this等等。on和add的相关处理代码上我进行了详细注释,这里不说了。
和on对应的就是off,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)
})
}
一看off的代码基本上处理处理相关参数,剩下没啥了,最后都聚焦到remove的实现上,所以重点关注remove的实现,关注remove的同时需要知道findHandlers
//删除绑定在元素上的指定类型的事件监听函数,可同时删除多种事件类型指定的函数,用数组或者还空格的字符串即可,同add
function remove(element, events, fn, selector, capture) {
//取到元素的zid
var id = zid(element);
(events || '').split(/\s/).forEach(function(event) {
findHandlers(element, event, fn, selector).forEach(function(handler) {
//删除handlers 对应这个元素(通过zid关联的),对应的索引的callback。
//var a=[1,2,3,4,5] delete a[0],delete a[3]====>[2,3,5]
delete handlers[id][handler.i]
//移除元素上绑定的事件
if ('removeEventListener' in element)
element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
})
})
}
//查找绑定在元素上的指定类型的事件处理函数集合
function findHandlers(element, event, fn, selector) {
event = parse(event)
if (event.ns) var matcher = matcherFor(event.ns)
return (handlers[zid(element)] || []).filter(function(handler) {
//判断事件命名空间是否相同
//注意函数是引用类型的数据zid(handler.fn)的作用是返回handler.fn的标示符,如果没有,则给它添加一个,
//这样如果fn和handler.fn引用的是同一个函数,那么fn上应该也可相同的标示符,
//这里就是通过这一点来判断两个变量是否引用的同一个函数
return handler && (!event.e || handler.e == event.e) &&
(!event.ns || matcher.test(handler.ns)) && (!fn || zid(handler.fn) === zid(fn)) && (!selector || handler.sel == selector)
})
}
最后看看trigger和triggerHandler的实现,用法上面已经说过了看实现。两者的差异代码注释里面说了,一个是dispatchEvent(event),一个是直接调用handler.proxy(e)。相当于一个经过了浏览器的相关行为,另一个根本没走浏览器,直接调用了元素上绑定事件的对应回调函数。
$.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)
})
} // triggers event handlers on current element just as if an event occurred,
// doesn't trigger an actual event, doesn't bubble
//触发元素上绑定的指定类型的事件,但是不冒泡
$.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
//遍历元素上绑定的指定类型的事件处理函数集,按顺序执行,如果执行过stopImmediatePropagation方法,
//那么e.isImmediatePropagationStopped()就会返回true,再外层函数返回false
//each里的回调函数指定返回false时,会跳出循环,这样就达到的停止执行回面函数的目的
$.each(findHandlers(element, event.type || event), function(i, handler) {
//直接调用handler.proxy发方法,没有经过浏览器,所以很多浏览器的行为不会发生。
// $("input").triggerHandler('focus');
// 此时input上的focus事件触发,但是input不会获取焦点。因为这里直接取到绑定到该元素对应的focus事件,然后调用
//$("input").trigger('focus');
// 此时input上的focus事件触发,input获取焦点。这里最后会dispatchEvent,会触发浏览器相关行为
result = handler.proxy(e)
//如果这个对象调用了ImmediatePropagationStopped方法
if (e.isImmediatePropagationStopped()) return false
})
})
return result
}
代码具体实现还得自己去折腾,没什么特别复杂的,花点事件肯定能够看懂,看不懂多google就行。zepto 的时间设计比起jquery简单很多很多,所以看懂这个再去啃jqeury吧,jqeury看得头大。最后附上事件处理的全部代码,都是自己加上的注释。
;
(function($) {
var _zid = 1,
undefined,
slice = Array.prototype.slice,
isFunction = $.isFunction,
isString = function(obj) {
return typeof obj == 'string'
},
handlers = {},
specialEvents = {},
focusinSupported = 'onfocusin' in window,
focus = {
focus: 'focusin',
blur: 'focusout'
},
hover = {
mouseenter: 'mouseover',
mouseleave: 'mouseout'
}
//特殊事件
specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents' //取element的唯一标示符,如果没有,则设置一个并返回 ,保证-zid的唯一性
function zid(element) {
return element._zid || (element._zid = _zid++)
}
//查找绑定在元素上的指定类型的事件处理函数集合
function findHandlers(element, event, fn, selector) {
event = parse(event)
if (event.ns) var matcher = matcherFor(event.ns)
return (handlers[zid(element)] || []).filter(function(handler) {
//判断事件命名空间是否相同
//注意函数是引用类型的数据zid(handler.fn)的作用是返回handler.fn的标示符,如果没有,则给它添加一个,
//这样如果fn和handler.fn引用的是同一个函数,那么fn上应该也可相同的标示符,
//这里就是通过这一点来判断两个变量是否引用的同一个函数
return handler && (!event.e || handler.e == event.e) &&
(!event.ns || matcher.test(handler.ns)) && (!fn || zid(handler.fn) === zid(fn)) && (!selector || handler.sel == selector)
})
}
//解析事件类型,返回一个包含事件名称和事件命名空间的对象
function parse(event) {
var parts = ('' + event).split('.')
return {
e: parts[0],
//name space
ns: parts.slice(1).sort().join(' ')
}
}
//生成命名空间的正则
function matcherFor(ns) {
return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)')
}
//通过给focus和blur事件设置为捕获来达到事件冒泡的目的
function eventCapture(handler, captureSetting) {
return handler.del &&
(!focusinSupported && (handler.e in focus)) ||
!!captureSetting
}
//修复不支持mouseenter和mouseleave的情况
function realEvent(type) {
return hover[type] || (focusinSupported && focus[type]) || type
} function add(element, events, fn, data, selector, delegator, capture) {
//取到元素的zid
var id = zid(element),
set = (handlers[id] || (handlers[id] = [])) //元素上已经绑定的所有事件处理函数,如果没有赋值一个新数组 events.split(/\s/).forEach(function(event) {
//如果是绑定dom ready事件
if (event == 'ready') return $(document).ready(fn)
//解析事件类型,返回一个包含事件名称和事件命名空间的对象
var handler = parse(event)
// //保存fn,下面为了处理mouseenter, mouseleave时,对fn进行了修改
handler.fn = fn
handler.sel = selector
// emulate mouseenter, mouseleave
// 模仿 mouseenter, mouseleave
if (handler.e in hover) fn = function(e) {
/*
relatedTarget为事件相关对象,只有在mouseover和mouseout事件时才有值
mouseover时表示的是鼠标移出的那个对象,mouseout时表示的是鼠标移入的那个对象
当related不存在,表示事件不是mouseover或者mouseout,mouseover时!$.contains(this, related)当相关对象不在事件对象内
且related !== this相关对象不是事件对象时,表示鼠标已经从事件对象外部移入到了对象本身,这个时间是要执行处理函数的
当鼠标从事件对象上移入到子节点的时候related就等于this了,且!$.contains(this, related)也不成立,这个时间是不需要执行处理函数的
*/
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)
//这个event对象执行过阻止冒泡方法stopImmediatePropagation,这里直接返回。
if (e.isImmediatePropagationStopped()) return
e.data = data
//调用之前传入的回调函数
var result = callback.apply(element, e._args == undefined ? [e] : [e].concat(e._args))
//当事件处理函数返回false时,阻止默认操作和冒泡
if (result === false) e.preventDefault(), e.stopPropagation()
return result
}
//设置处理函数的在函数集中的位置,remove的时候要用到
handler.i = set.length
//将函数存入函数集中,引用类型,你懂的,handlers里面也有了
set.push(handler)
if ('addEventListener' in element)
//realEvent(handler.e) 处理事件类型,eventCapture绑定事件类型,是捕获还是冒泡
element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
})
} //删除绑定在元素上的指定类型的事件监听函数,可同时删除多种事件类型指定的函数,用数组或者还空格的字符串即可,同add
function remove(element, events, fn, selector, capture) {
//取到元素的zid
var id = zid(element);
(events || '').split(/\s/).forEach(function(event) {
findHandlers(element, event, fn, selector).forEach(function(handler) {
//删除handlers 对应这个元素(通过zid关联的),对应的索引的callback。
//var a=[1,2,3,4,5] delete a[0],delete a[3]====>[2,3,5]
delete handlers[id][handler.i]
//移除元素上绑定的事件
if ('removeEventListener' in element)
element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
})
})
} $.event = {
add: add,
remove: remove
}
//看到他 就想起了bind
$.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")
}
} $.fn.bind = function(event, data, callback) {
return this.on(event, data, callback)
}
$.fn.unbind = function(event, callback) {
return this.off(event, callback)
} $.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]$)/,
eventMethods = {
//是否调用过preventDefault方法
preventDefault: 'isDefaultPrevented',
//取消执行其他的事件处理函数并取消事件冒泡.如果同一个事件绑定了多个事件处理函数, 在其中一个事件处理函数中调用此方法后将不会继续调用其他的事件处理函数.
stopImmediatePropagation: 'isImmediatePropagationStopped', //是否调用过stopImmediatePropagation方法,
stopPropagation: 'isPropagationStopped' //是否调用过stopPropagation方法
} //主要是在event和source做相关的处理
function compatible(event, source) {
//存在source 或者 event的isDefaultPrevented不存在
if (source || !event.isDefaultPrevented) {
source || (source = event)
$.each(eventMethods, function(name, predicate) {
//source['preventDefault']、source['stopImmediatePropagation']、source['stopPropagation']
var sourceMethod = source[name]
//event['preventDefault']、event['stopImmediatePropagation']、event['stopPropagation']
event[name] = function() {
//this['isDefaultPrevented']this['isImmediatePropagationStopped']this['isPropagationStopped']
//一旦调用过,event对象相应的值就会发生变化, 之前是returnFalse,现在是returnTrue
this[predicate] = returnTrue
return sourceMethod && sourceMethod.apply(source, arguments)
}
//event['isDefaultPrevented']、event['isImmediatePropagationStopped']、event['isPropagationStopped']
event[predicate] = returnFalse
}) if (source.defaultPrevented !== undefined ? source.defaultPrevented :
'returnValue' in source ? source.returnValue === false :
source.getPreventDefault && source.getPreventDefault()) event.isDefaultPrevented = returnTrue
}
return event
} function createProxy(event) {
var key, proxy = {
originalEvent: event //保存原始event
}
for (key in event)
//不是需要忽略的
if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key] //复制event属性至proxy return compatible(proxy, event)
} $.fn.delegate = function(selector, event, callback) {
return this.on(event, selector, callback)
}
$.fn.undelegate = function(selector, event, callback) {
return this.off(event, selector, callback)
}
$.fn.live = function(event, callback) {
//委托到body上
$(document.body).delegate(this.selector, event, callback)
return this
}
$.fn.die = function(event, callback) {
$(document.body).undelegate(this.selector, event, callback)
return this
}
$.fn.on = function(event, selector, data, callback, one) {
var autoRemove, delegator, $this = this
//如果是{'click':function(){},'touchmove':function(){}}
//此时event是Object
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 (isFunction(data) || 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)
}
//事件委托,这里是事件冒泡到element元素上
if (selector) delegator = function(e) {
//事件触发元素e.target的祖先级元素
var evt, match = $(e.target).closest(selector, element).get(0)
//找到了 并且不是element本身
if (match && match !== element) {
//创建一个event对象
evt = $.extend(createProxy(e), {
currentTarget: match,//匹配到的元素
liveFired: element//委托的元素
})
//(autoRemove || callback)不是一次性事件,就调用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)
})
}
$.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)
})
}
$.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)
})
} // triggers event handlers on current element just as if an event occurred,
// doesn't trigger an actual event, doesn't bubble
//触发元素上绑定的指定类型的事件,但是不冒泡
$.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
//遍历元素上绑定的指定类型的事件处理函数集,按顺序执行,如果执行过stopImmediatePropagation方法,
//那么e.isImmediatePropagationStopped()就会返回true,再外层函数返回false
//each里的回调函数指定返回false时,会跳出循环,这样就达到的停止执行回面函数的目的
$.each(findHandlers(element, event.type || event), function(i, handler) {
//直接调用handler.proxy发方法,没有经过浏览器,所以很多浏览器的行为不会发生。
// $("input").triggerHandler('focus');
// 此时input上的focus事件触发,但是input不会获取焦点。因为这里直接取到绑定到该元素对应的focus事件,然后调用
//$("input").trigger('focus');
// 此时input上的focus事件触发,input获取焦点。这里最后会dispatchEvent,会触发浏览器相关行为
result = handler.proxy(e)
//如果这个对象调用了ImmediatePropagationStopped方法
if (e.isImmediatePropagationStopped()) return false
})
})
return result
} // shortcut methods for `.bind(event, fn)` for each event type
;
('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) ?
//多个参数
this.bind(event, callback) :
//没有参数 直接调用
this.trigger(event)
}
})
//根据参数创建一个event对象
$.Event = function(type, props) {
//当type是个对象时
if (!isString(type)) props = type, type = props.type
//创建一个event对象,如果是click,mouseover,mouseout时,创建的是MouseEvent,bubbles为是否冒泡
var event = document.createEvent(specialEvents[type] || 'Events'),
bubbles = true
//确保bubbles的值为true或false,并将props参数的属性扩展到新创建的event对象上
if (props)
for (var name in props)(name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name])
//初始化event对象,type为事件类型,如click,bubbles为是否冒泡,第三个参数表示是否可以用preventDefault方法来取消默认操作
event.initEvent(type, bubbles, true)
return compatible(event)
} })(Zepto);
zepto event