读Zepto源码之Event模块
2017-07-31 09:21
447 查看
Event 模块是 Zepto 必备的模块之一,由于对 Event Api 不太熟,Event 对象也比较复杂,所以乍一看 Event 模块的源码,有点懵,细看下去,其实也不太复杂。
读Zepto源码系列文章已经放到了github上,欢迎star: reading-zepto
本文阅读的源码为 zepto1.2.0
为什么要对
MDN 中可以看到,
除了
我们可以通过以下代码来确定这四个事件的执行顺序:
在
可以看到,在此浏览器中,事件的执行顺序应该是
关于这几个事件更详细的描述,可以查看:《说说focus /focusin /focusout /blur 事件》
关于事件的执行顺序,我测试的结果与文章所说的有点不太一样。感兴趣的可以点击这个链接测试下http://jsbin.com/nizugazamo/edit?html,js,console,output。不过我觉得执行顺序可以不必细究,可以将
跟
但是
在鼠标事件的
另外
因此,要模拟
关于
将
其实就是向闭包中传入
在
提供简洁的API
统一不同浏览器的
事件句柄缓存池,方便手动触发事件和解绑事件。
事件委托
获取参数
在
返回的对象中,
生成匹配命名空间的表达式,例如,传进来的参数
查找元素对应的事件句柄。
调用
如果命名空间存在,则生成匹配该命名空间的正则表达式
返回的其实是
返回的句柄必须满足5个条件:
句柄必须存在
如果
如果命名空间存在,则句柄的命名空间必须要与事件的命名空间匹配(
)
如果指定匹配的事件句柄为
如果指定选择器
从上面的比较可以看到,缓存的句柄对象的形式如下:
这个函数其实是将
由于
判断条件是,原事件对象存在,或者事件
如果
作为原事件对象。
遍历
改写
,例如执行
这是将新添加的属性,初始化为
这段向不支持
这是对浏览器
如果浏览器支持
则返回
如果浏览器支持
判断为
遍历原生事件对象,排除掉不需要的属性和值为
最终返回的是修正后的代理对象
返回
如果存在事件代理,并且事件为
在捕获阶段处理事件,间接达到冒泡的目的。
否则作用自定义的
获取或设置
对每个事件进行处理
如果为
这段代码是设置
这里主要看对
事件句柄的代理函数。
调用
再扩展
执行事件句柄,将
如果执行完毕后,显式返回
将句柄存入句柄容器
调用元素的
首先获取指定元素的
遍历需要删除的
调用
删除句柄容器中对应的事件,在
调用
将
代理函数,作用有点像 JS 中的
如果提供超过3个参数,则去除前两个参数,将后面的参数作为执行函数
这里判断
给代理后的函数加上
如果函数已经包含在上下文对象中,即第一个参数
如果参数存在时,将
如果
参数
如果不是字符串,也即是
调用
遍历
初始化新创建的事件,并将修正后的事件对象返回。
这段是处理
先来分析第一个
这里可以确定
因此这里将
再来看第二个
原来的
为
第三个
可以看到,这里是遍历元素集合,为每个元素都调用
如果只调用一次,设置
如果
调用
如果
最后执行句柄函数,以代理元素
将该函数赋给
解绑事件
这段逻辑与
第一个
第二个
最后遍历所有元素,调用
事件委托,也是调用
取消事件委托,内部调用的是
动态创建的节点也可以响应事件。其实事件绑定在
将由
直接触发事件回调函数。
参数
如果
调用
如果返回的结果
由于
手动触发事件。
如果传递的是字符串或者纯粹对象,则先调用
如果是
如果
EventTarget.dispatchEvent()。
否则,直接调用
由于
读Zepto源码系列文章已经放到了github上,欢迎star: reading-zepto
源码版本
本文阅读的源码为 zepto1.2.0
准备知识
focus/blur 的事件模拟
为什么要对 focus和
blur事件进行模拟呢?从
MDN 中可以看到,
focus事件和
blur事件并不支持事件冒泡。不支持事件冒泡带来的直接后果是不能进行事件委托,所以需要对
focus和
blur事件进行模拟。
除了
focus事件和
blur事件外,现代浏览器还支持
focusin事件和
focusout事件,他们和
focus事件及
blur事件的最主要区别是支持事件冒泡。因此可以用
focusin和模拟
focus事件的冒泡行为,用
focusout事件来模拟
blur事件的冒泡行为。
我们可以通过以下代码来确定这四个事件的执行顺序:
<input id="test" type="text" />
const target = document.getElementById('test') target.addEventListener('focusin', () => {console.log('focusin')}) target.addEventListener('focus', () => {console.log('focus')}) target.addEventListener('blur', () => {console.log('blur')}) target.addEventListener('focusout', () => {console.log('focusout')})
在
chrome59下,
input聚焦和失焦时,控制台会打印出如下结果:
'focus' 'focusin' 'blur' 'focusout'
可以看到,在此浏览器中,事件的执行顺序应该是
focus > focusin > blur > focusout
关于这几个事件更详细的描述,可以查看:《说说focus /focusin /focusout /blur 事件》
关于事件的执行顺序,我测试的结果与文章所说的有点不太一样。感兴趣的可以点击这个链接测试下http://jsbin.com/nizugazamo/edit?html,js,console,output。不过我觉得执行顺序可以不必细究,可以将
focusin作为
focus事件的冒泡版本。
mouseenter/mouseleave 的事件模拟
跟 focus和
blur一样,
mouseenter和
mouseleave也不支持事件的冒泡,
但是
mouseover和
mouseout支持事件冒泡,因此,这两个事件的冒泡处理也可以分别用
mouseover和
mouseout来模拟。
在鼠标事件的
event对象中,有一个
relatedTarget的属性,从 MDN:MouseEvent.relatedTarget 文档中,可以看到,
mouseover的
relatedTarget指向的是移到目标节点上时所离开的节点(
exited from),
mouseout的
relatedTarget所指向的是离开所在的节点后所进入的节点(
entered to)。
另外
mouseover事件会随着鼠标的移动不断触发,但是
mouseenter事件只会在进入节点的那一刻触发一次。如果鼠标已经在目标节点上,那
mouseover事件触发时的
relatedTarget为当前节点。
因此,要模拟
mousee 20000 nter或
mouseleave事件,只需要确定触发
mouseover或
mouseout事件上的
relatedTarget不存在,或者
relatedTarget不为当前节点,并且不为当前节点的子节点,避免子节点事件冒泡的影响。
关于
mouseenter和
mouseleave的模拟, 谦龙 有篇文章《mouseenter与mouseover为何这般纠缠不清?》写得很清楚,建议读一下。
Event 模块的核心
将 Event模块简化后如下:
;(function($){})(Zepto)
其实就是向闭包中传入
Zepto对象,然后对
Zepto对象做一些扩展。
在
Event模块中,主要做了如下几件事:
提供简洁的API
统一不同浏览器的
event对象
事件句柄缓存池,方便手动触发事件和解绑事件。
事件委托
内部方法
zid
var _zid = 1 function zid(element) { return element._zid || (element._zid = _zid++) }
获取参数
element对象的
_zid属性,如果属性不存在,则全局变量
_zid增加
1,作为
element的
_zid的属性值返回。这个方法用来标记已经绑定过事件的元素,方便查找。
parse
function parse(event) { var parts = ('' + event).split('.') return {e: parts[0], ns: parts.slice(1).sort().join(' ')} }
在
zepto中,支持事件的命名空间,可以用
eventType.ns1.ns2...的形式来给事件添加一个或多个命名空间。
parse函数用来分解事件名和命名空间。
'' + event是将
event变成字符串,再以
.分割成数组。
返回的对象中,
e为事件名,
ns为排序后,以空格相连的命名空间字符串,形如
ns1 ns2 ns3 ...的形式。
matcherFor
function matcherFor(ns) { return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)') }
生成匹配命名空间的表达式,例如,传进来的参数
ns为
ns1 ns2 ns3,最终生成的正则为
/(?:^| )ns1.* ?ns2.* ?ns3(?: |$)/。至于有什么用,下面马上讲到。
findHandlers,查找缓存的句柄
handlers = {} 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)) && (!fn || zid(handler.fn) === zid(fn)) && (!selector || handler.sel == selector) }) }
查找元素对应的事件句柄。
event = parse(event)
调用
parse函数,分隔出
event参数的事件名和命名空间。
if (event.ns) var matcher = matcherFor(event.ns)
如果命名空间存在,则生成匹配该命名空间的正则表达式
matcher。
return (handlers[zid(element)] || []).filter(function(handler) { ... })
返回的其实是
handlers[zid(element)]中符合条件的句柄函数。
handlers是缓存的句柄容器,用
element的
_zid属性值作为
key。
javascript return handler // 条件1 && (!event.e || handler.e == event.e) // 条件2 && (!event.ns || matcher.test(handler.ns)) // 条件3 && (!fn || zid(handler.fn) === zid(fn)) // 条件4 && (!selector || handler.sel == selector) // 条件5
返回的句柄必须满足5个条件:
句柄必须存在
如果
event.e存在,则句柄的事件名必须与
event的事件名一致
如果命名空间存在,则句柄的命名空间必须要与事件的命名空间匹配(
matcherFor的作用
)
如果指定匹配的事件句柄为
fn,则当前句柄
handler的
_zid必须与指定的句柄
fn相一致
如果指定选择器
selector,则当前句柄中的选择器必须与指定的选择器一致
从上面的比较可以看到,缓存的句柄对象的形式如下:
{ fn: '', // 函数 e: '', // 事件名 ns: '', // 命名空间 sel: '', // 选择器 // 除此之外,其实还有 i: '', // 函数索引 del: '', // 委托函数 proxy: '', // 代理函数 // 后面这几个属性会讲到 }
realEvent,返回对应的冒泡事件
focusinSupported = 'onfocusin' in window, focus = { focus: 'focusin', blur: 'focusout' }, hover = { mouseenter: 'mouseover', mouseleave: 'mouseout' } function realEvent(type) { return hover[type] || (focusinSupported && focus[type]) || type }
这个函数其实是将
focus/blur转换成
focusin/focusout,将
mouseenter/mouseleave转换成
mouseover/mouseout事件。
由于
focusin/focusout事件浏览器支持程度还不是很好,因此要对浏览器支持做一个检测,如果浏览器支持,则返回,否则,返回原事件名。
compatible,修正event对象
returnTrue = function(){return true}, returnFalse = function(){return false}, 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 }) try { event.timeStamp || (event.timeStamp = Date.now()) } catch (ignored) { } if (source.defaultPrevented !== undefined ? source.defaultPrevented : 'returnValue' in source ? source.returnValue === false : source.getPreventDefault && source.getPreventDefault()) event.isDefaultPrevented = returnTrue } return event }
compatible函数用来修正
event对象的浏览器差异,向
event对象中添加了
isDefaultPrevented、
isImmediatePropagationStopped、
isPropagationStopped几个方法,对不支持
timeStamp的浏览器,向
event对象中添加
timeStamp属性。
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的
isDefaultPrevented不存在时成立。
如果
source不存在,则将
event赋值给
source,
作为原事件对象。
遍历
eventMethods,获得原事件对象的对应方法名
sourceMethod。
event[name] = function(){ this[predicate] = returnTrue return sourceMethod && sourceMethod.apply(source, arguments) }
改写
event对象相应的方法,如果执行对应的方法时,先将事件中方法所对应的新方法赋值为
returnTrue函数
,例如执行
preventDefault方法时,
isDefaultPrevented方法的返回值为
true。
event[predicate] = returnFalse
这是将新添加的属性,初始化为
returnFalse方法
try { event.timeStamp || (event.timeStamp = Date.now()) } catch (ignored) { }
这段向不支持
timeStamp属性的浏览器中添加
timeStamp属性。
if (source.defaultPrevented !== undefined ? source.defaultPrevented : 'returnValue' in source ? source.returnValue === false : source.getPreventDefault && source.getPreventDefault()) event.isDefaultPrevented = returnTrue }
这是对浏览器
preventDefault不同实现的兼容。
source.defaultPrevented !== undefined ? source.defaultPrevented : '三元表达式'
如果浏览器支持
defaultPrevented,
则返回
defaultPrevented的值
'returnValue' in source ? source.returnValue === false : '后一个判断'
returnValue默认为
true,如果阻止了浏览器的默认行为,
returnValue会变为
false。
source.getPreventDefault && source.getPreventDefault()
如果浏览器支持
getPreventDefault方法,则调用
getPreventDefault()方法获取是否阻止浏览器的默认行为。
判断为
true的时候,将
isDefaultPrevented设置为
returnTrue方法。
createProxy,创建代理对象
ignoreProperties = /^([A-Z]|returnValue$|layer[XY]$|webkitMovement[XY]$)/, 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) }
zepto中,事件触发的时候,返回给我们的
event都不是原生的
event对象,都是代理对象,这个就是代理对象的创建方法。
ignoreProperties用来排除
A-Z开头,即所有大写字母开头的属性,还有以
returnValue结尾,
layerX/layerY,
webkitMovementX/webkitMovementY结尾的非标准属性。
for (key in event) if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key]
遍历原生事件对象,排除掉不需要的属性和值为
undefined的属性,将属性和值复制到代理对象上。
最终返回的是修正后的代理对象
eventCapture
function eventCapture(handler, captureSetting) { return handler.del && (!focusinSupported && (handler.e in focus)) || !!captureSetting }
返回
true表示在捕获阶段执行事件句柄,否则在冒泡阶段执行。
如果存在事件代理,并且事件为
focus/blur事件,在浏览器不支持
focusin/focusout事件时,设置为
true,
在捕获阶段处理事件,间接达到冒泡的目的。
否则作用自定义的
captureSetting设置事件执行的时机。
add,Event 模块的核心方法
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方法是向元素添加事件及事件响应,参数比较多,先来看看各参数的含义:
element // 事件绑定的元素 events // 需要绑定的事件列表 fn // 事件执行时的句柄 data // 事件执行时,传递给事件对象的数据 selector // 事件绑定元素的选择器 delegator // 事件委托函数 capture // 那个阶段执行事件句柄
var id = zid(element), set = (handlers[id] || (handlers[id] = []))
获取或设置
id,
set为事件句柄容器。
events.split(/\s/).forEach(function(event){})
对每个事件进行处理
if (event == 'ready') return $(document).ready(fn)
如果为
ready事件,则调用
ready方法,中止后续的执行
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上的一些属性,缓存起来。
这里主要看对
mouseenter和
mouseleave事件的模拟,具体的原理上面已经说过,只有在条件成立的时候才会执行事件句柄。
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为事件执行时的原生
event对象,因此先调用
compatible对
e进行修正。
调用
isImmediatePropagationStopped方法,看是否已经执行过
stopImmediatePropagation方法,如果已经执行,则中止后续程序的执行。
再扩展
e对象,将
data存到
e的
data属性上。
执行事件句柄,将
e对象作为句柄的第一个参数。
如果执行完毕后,显式返回
false,则阻止浏览器的默认行为和事件冒泡。
set.push(handler) if ('addEventListener' in element) element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
将句柄存入句柄容器
调用元素的
addEventListener方法,添加事件,事件的回调函数用的是句柄的代理函数,
eventCapture(handler, capture)来用指定是否在捕获阶段执行。
remove,删除事件
function remove(element, events, fn, selector, capture){ var id = zid(element) ;(events || '').split(/\s/).forEach(function(event){ findHandlers(element, event, fn, selector).forEach(function(handler){ delete handlers[id][handler.i] if ('removeEventListener' in element) element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture)) }) }) }
首先获取指定元素的
_zid
;(events || '').split(/\s/).forEach(function(event){})
遍历需要删除的
events
findHandlers(element, event, fn, selector).forEach(function(handler){})
调用
findHandlers方法,查找
event下需要删除的事件句柄
delete handlers[id][handler.i]
删除句柄容器中对应的事件,在
add函数中的句柄对象中的
i属性就用在这里了,方便查找需要删除的句柄。
element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
调用
removeEventListener方法,删除对应的事件。
工具函数
$.event
$.event = { add: add, remove: remove }
将
add方法和
remove方法暴露出去,应该是方便第三方插件做扩展
$.proxy
$.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") } }
代理函数,作用有点像 JS 中的
bind方法,返回的是一个代理后改变执行上下文的函数。
var args = (2 in arguments) && slice.call(arguments, 2)
如果提供超过3个参数,则去除前两个参数,将后面的参数作为执行函数
fn的参数。
if (isFunction(fn)) { var proxyFn = function(){ return fn.apply(context, args ? args.concat(slice.call(arguments)) : arguments) } proxyFn._zid = zid(fn) return proxyFn }
proxy的执行函数有两种传递方式,一是在第一个参数直接传入,二是第一个参数为上下文对象,执行函数也在上下文对象中一起传入。
这里判断
fn是否为函数,即第一种传参方式,调用
fn函数的
apply方法,将上下文对象
context作为
apply的第一个参数,如果
args存在,则与
fn的参数合并。
给代理后的函数加上
_zid属性,方便函数的查找。
else if (isString(context)) { if (args) { args.unshift(fn[context], fn) return $.proxy.apply(null, args) } else { return $.proxy(fn[context], fn) }
如果函数已经包含在上下文对象中,即第一个参数
fn为对象,第二个参数
context为字符串,用来指定执行函数的在上下文对象中的属性名。
if (args) { args.unshift(fn[context], fn) return $.proxy.apply(null, args) }
如果参数存在时,将
fn[context],也即执行函数和
fn,也即上下文对象放入
args数组的开头,这样就将参数修正成跟第一种传参方式一样,再调用
$.proxy函数。这里调用
apply方法,是因为不知道参数有多少个,调用
apply可以以数组的形式传入。
如果
args不存在时,确定的参数项只有两个,因此可以直接调用
$.proxy方法。
$.Event
specialEvents={}, specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents' $.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) }
specialEvents是将鼠标事件修正为
MouseEvents,这应该是处理浏览器的兼容问题,可能有些浏览器中,这些事件的事件类型并不是
MouseEvents。
$.Event方法用来手动创建特定类型的事件。
参数
type可以为字符串,也可以为
event对象。
props为扩展
event对象的对象。
if (!isString(type)) props = type, type = props.type
如果不是字符串,也即是
event对象时,将
type赋给
props,
type为当前
event对象中的
type属性值。
var event = document.createEvent(specialEvents[type] || 'Events'), bubbles = true
调用
createEvent方法,创建对应类型的
event事件,并将事件冒泡默认设置为
true
if (props) for (var name in props) (name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name])
遍历
props属性,如果有指定
bubbles,则采用指定的冒泡行为,其他属性复制到
event对象上,实现对
event对象的扩展。
event.initEvent(type, bubbles, true) return compatible(event)
初始化新创建的事件,并将修正后的事件对象返回。
方法
.on()
$.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 }
autoRemove表示在执行完事件响应后,自动解绑的函数。
event可以为字符串或者对象,当为对象时,对象的属性为事件类型,属性值为句柄。
这段是处理
event为对象时的情况,遍历对象,得到事件类型和句柄,然后再次调用
on方法,继续修正后续的参数。
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
先来分析第一个
if,
selector不为
string,
callback不为函数,并且
callback不为
false时的情况。
这里可以确定
selector并没有传递,因为
selector不是必传的参数。
因此这里将
data赋给
callback,
selector赋给
data,将
selector设置为
undefined,因为
selector没有传递,因此相应参数的位置都前移了一位。
再来看第二个
if,如果
callback(
原来的
data)
为
undefined,
data为
false时,表示
selector没有传递,并且
data也没有传递,因此将
data赋给
callback,将
data设置为
undefined,即将参数再前移一位。
第三个
if,如果
callback === false,用
returnFalse函数代替,如果不用
returnFalse代替,会报错。
return $this.each(function(_, element){ add(element, event, callback, data, selector, delegator || autoRemove) })
可以看到,这里是遍历元素集合,为每个元素都调用
add方法,绑定事件。
if (one) autoRemove = function(e){ remove(element, e.type, callback) return callback.apply(this, arguments) }
如果只调用一次,设置
autoRemove为一个函数,这个函数在句柄执行前,调用
remove方法,将绑定在元素上对应事件解绑。
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))) } }
如果
selector存在,表示需要做事件代理。
调用
closest方法,从事件的目标元素
e.target开始向上查找,返回第一个匹配
selector的元素。关于
closest方法,见《读Zepto源码之集合元素查找》分析。
如果
match存在,并且
match不为当前元素,则调用
createProxy方法,为当前事件对象创建代理对象,再调用
$.extend方法,为代理对象扩展
currentTarget和
liveFired属性,将代理元素和触发事件的元素保存到事件对象中。
最后执行句柄函数,以代理元素
match作为句柄的上下文,用代理后的
event对象
evt替换掉原句柄函数的第一个参数。
将该函数赋给
delegator,作为代理函数传递给
add方法。
.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) }) }
解绑事件
if (event && !isString(event)) { $.each(event, function(type, fn){ $this.off(type, selector, fn) }) return $this }
这段逻辑与
on方法中的相似,修正参数,不再细说。
if (!isString(selector) && !isFunction(callback) && callback !== false) callback = selector, selector = undefined if (callback === false) callback = returnFalse
第一个
if是处理
selector参数没有传递的情况的,
selector位置传递的其实是
callback。
第二个
if是判断如果
callback为
false,将
callback赋值为
returnFalse函数。
return $this.each(function(){ remove(this, event, callback, selector) })
最后遍历所有元素,调用
remove函数,为每个元素解绑事件。
.bind()
$.fn.bind = function(event, data, callback){ return this.on(event, data, callback) }
bind方法内部调用的其实是
on方法。
.unbind()
$.fn.unbind = function(event, callback){ return this.off(event, callback) }
unbind方法内部调用的是
off方法。
.one()
$.fn.one = function(event, selector, data, callback){ return this.on(event, selector, data, callback, 1) }
one方法内部调用的也是
on方法,只不过默认传递了
one参数为
1,表示绑定的事件只执行一下。
.delegate()
$.fn.delegate = function(selector, event, callback){ return this.on(event, selector, callback) }
事件委托,也是调用
on方法,只是
selector一定要传递。
.undelegate()
$.fn.undelegate = function(selector, event, callback){ return this.off(event, selector, callback) }
取消事件委托,内部调用的是
off方法,
selector必须要传递。
.live()
$.fn.live = function(event, callback){ $(document.body).delegate(this.selector, event, callback) return this }
动态创建的节点也可以响应事件。其实事件绑定在
body上,然后委托到当前节点上。内部调用的是
delegate方法。
.die()
$.fn.die = function(event, callback){ $(document.body).undelegate(this.selector, event, callback) return this }
将由
live绑定在
body上的事件销毁,内部调用的是
undelegate方法。
.triggerHandler()
$.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 }
直接触发事件回调函数。
参数
event可以为事件类型字符串,也可以为
event对象。
javascript e = createProxy(isString(event) ? $.Event(event) : event)
如果
event为字符串时,则调用
$.Event工具函数来初始化一个事件对象,再调用
createProxy来创建一个
event代理对象。
$.each(findHandlers(element, event.type || event), function(i, handler){ result = handler.proxy(e) if (e.isImmediatePropagationStopped()) return false })
调用
findHandlers方法来找出事件的所有句柄,调用
proxy方法,即真正绑定到事件上的回调函数(参见
add的解释),拿到方法返回的结果
result,并查看
isImmediatePropagationStopped返回的结果是否为
true,如果是,立刻中止后续执行。
如果返回的结果
result为
false,也立刻中止后续执行。
由于
triggerHandler直接触发回调函数,所以事件不会冒泡。
.trigger()
$.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) }) }
手动触发事件。
event = (isString(event) || $.isPlainObject(event)) ? $.Event(event) : compatible(event)
event可以传递事件类型,对象和
event对象。
如果传递的是字符串或者纯粹对象,则先调用
$.Event方法来初始化事件,否则调用
compatible方法来修正
event对象,由于
$.Event方法在内部其实已经调用过
compatible方法修正
event对象了的,所以外部不需要再调用一次。
if (event.type in focus && typeof this[event.type] == "function") this[event.type]()
如果是
focus/blur方法,则直接调用
this.focus()或
this.blur()方法,这两个方法是浏览器原生支持的。
如果
this为
DOM元素,即存在
dispatchEvent方法,则用
dispatchEvent来触发事件,关于
dispatchEvent,可以参考 MDN:
EventTarget.dispatchEvent()。
否则,直接调用
triggerHandler方法来触发事件的回调函数。
由于
trigger是通过触发事件来执行事件句柄的,因此事件会冒泡。
相关文章推荐
- 读Zepto源码之Event模块
- 读Zepto源码之Event模块
- 一个普通的 Zepto 源码分析(三) - event 模块
- 读Zepto源码之Event模块
- 读Zepto源码之Event模块
- 一个普通的 Zepto 源码分析(一) - ie 与 form 模块
- Zepto源码分析-form模块
- nginx 源码学习笔记(二十三)—— event 模块(四) ——timer红黑树
- zepto源码学习-04 event
- 读Zepto源码之Gesture模块
- 读Zepto源码之Stack模块
- Zepto源码之data模块
- 读Zepto源码之Touch模块
- Zepto源码之fx模块
- 一步一步DIY zepto库,研究zepto源码1--基础模块
- 读Zepto源码之Ajax模块
- nginx 源码学习笔记(二十二)—— event 模块(三) ——epoll模块
- Nginx源码剖析--event类型模块
- Zepto源码之touch模块
- 读Zepto源码之Data模块