第一,通过obj.addEventListener("click",fn)绑定的事件,你不能通过obj.onclick = null;来移除绑定点击事件的所有回调函数。
所以引入第三方对象空间handler,来对用最终add函数绑定的事件,进行事件管理。
具体如何管理,
首先是以html element为key,它的value是一个数组,数组元素handler是zepto封装的事件对象。其对象为
  1. del: undefined
  2. e: "click"
  3. fn: function (){
  4. i: 0
  5. ns: "namespace"
  6. proxy: function (e) {
  7. sel: undefined
其中del是事件委托,e是事件名称,fn是事件回调函数,i是事件在数组里的索引,ns是命名空间,proxy是事件代理(为后面的trigger函数做准备),sel是选择器(这个是用来查找指定元素的zepto事件对象数组,findHandlers会用到)
这样就可以通过$(obj).off()遍历已绑定的事件,进行事件移除。
第二,事件委托
事件委托是通过父元素绑定事件,判断其target属性,获得子元素信息,间接实现子元素事件绑定效果。
zepto里的实现就是,目标元素target的closet也就是最近父元素了绑定事件。
第三,事件触发
通过事件模拟的方法,来触发事件。这样连自定义事件名称的事件,都能触发了。
 
/*
事件处理部份
*/
;
(function($) {
var $$ = $.zepto.qsa,
// _zid是每个事件绑定函数的标识符
handlers = {}, _zid = 1,
specialEvents = {},
hover = {
mouseenter: 'mouseover',
mouseleave: 'mouseout'
} specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents' //取element的唯一标示符,如果没有,则设置一个并返回 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) {
return handler && (!event.e || handler.e == event.e) //判断事件类型是否相同
&& (!event.ns || matcher.test(handler.ns)) //判断事件命名空间是否相同
//注意函数是引用类型的数据zid(handler.fn)的作用是返回handler.fn的标示符,如果没有,则给它添加一个,
//这样如果fn和handler.fn引用的是同一个函数,那么fn上应该也可相同的标示符,
//这里就是通过这一点来判断两个变量是否引用的同一个函数
&& (!fn || zid(handler.fn) === zid(fn)) && (!selector || handler.sel == selector)
})
}
//解析事件类型,返回一个包含事件名称和事件命名空间的对象 function parse(event) {
var parts = ('' + event).split('.')
return {
e: parts[0],
ns: parts.slice(1).sort().join(' ')
}
}
//生成命名空间的正则 function matcherFor(ns) {
return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)')
}
//遍历events
// events可能是1.["click","keyup"] 2."click keyup"
// fn就是事件处理函数
// iterator就是包装函数,外面再套一层的第三方函数
function eachEvent(events, fn, iterator) {
// 不是字符串,就当做数组处理
if ($.type(events) != "string") $.each(events, iterator)
// 字符串,则以空格为标识符,转数组,再进行枚举处理
else events.split(/\s/).forEach(function(type) {
iterator(type, fn)
})
}
//通过给focus和blur事件设置为捕获来达到事件冒泡的目的 function eventCapture(handler, captureSetting) {
return handler.del &&
(handler.e == 'focus' || handler.e == 'blur') || !! captureSetting
} //修复不支持mouseenter和mouseleave的情况 function realEvent(type) {
return hover[type] || type
} //给元素绑定监听事件,可同时绑定多个事件类型,如['click','mouseover','mouseout'],也可以是'click mouseover mouseout'
// add函数做了三件事
// 1.给添加的事件设置唯一id,以此来索引
// 2.对每个事件进行重新封装,有特殊事件需要修复的修复比如mouseover和mouseout,有委托的触发委托,有代理的触发代理
// 3.进行addEventListener绑定
function add(element, events, fn, selector, getDelegate, capture) {
var id = zid(element),
set = (handlers[id] || (handlers[id] = [])) //元素上已经绑定的所有事件处理函数
// 根据events的内容,进行遍历处理
// event是单个事件名称
eachEvent(events, fn, function(event, fn) {
var handler = parse(event)
//保存fn,下面为了处理mouseenter, mouseleave时,对fn进行了修改
handler.fn = fn
handler.sel = selector
//如果是 mouseenter, mouseleave,则转换成mouseover和mouseout处理
// 这里的参数handler.e是指 事件名称比如click
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 = getDelegate && getDelegate(fn, event)
var callback = handler.del || fn handler.proxy = function(e) {
// 给下面的trigger触发函数,进行e.data自定义增加数据
var result = callback.apply(element, [e].concat(e.data))
//当事件处理函数返回false时,阻止默认操作和冒泡
if (result === false) e.preventDefault(), e.stopPropagation()
return result
}
//设置处理函数的在函数集中的位置
handler.i = set.length
//将函数存入函数集中
set.push(handler)
element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
})
}
//删除绑定在元素上的指定类型的事件监听函数,可同时删除多种事件类型指定的函数,用数组或者还空格的字符串即可,同add
// 通过事件唯一标识符,来移除事件
function remove(element, events, fn, selector, capture) {
var id = zid(element)
eachEvent(events || '', fn, function(event, fn) {
findHandlers(element, event, fn, selector).forEach(function(handler) {
// 首先是移除事件管理对象的引用
delete handlers[id][handler.i]
// 其次是移除事件绑定
element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
})
})
} $.event = {
add: add,
remove: remove
} //设置代理
$.proxy = function(fn, context) {
if ($.isFunction(fn)) {
//如果fn是函数,则申明一个新的函数并用context作为上下文调用fn
var proxyFn = function() {
return fn.apply(context, arguments)
}
//引用fn标示符
proxyFn._zid = zid(fn)
return proxyFn
} else if (typeof context == 'string') {
return $.proxy(fn[context], fn)
} else {
throw new TypeError("expected function")
}
} $.fn.bind = function(event, callback) {
return this.each(function() {
add(this, event, callback)
})
}
$.fn.unbind = function(event, callback) {
return this.each(function() {
remove(this, event, callback)
})
}
//绑定一次性事件监听函数
$.fn.one = function(event, callback) {
return this.each(function(i, element) {
//添加函数,然后在回调函数里再删除绑定。达到一次性事件的目的
add(this, event, callback, null, function(fn, type) {
return function() {
var result = fn.apply(element, arguments) //这里执行绑定的回调
remove(element, type, fn) //删除上面的绑定
return result
}
})
})
} var returnTrue = function() {
return true
},
returnFalse = function() {
return false
},
ignoreProperties = /^([A-Z]|layer[XY]$)/,
eventMethods = {
preventDefault: 'isDefaultPrevented', //是否调用过preventDefault方法
//取消执行其他的事件处理函数并取消事件冒泡.如果同一个事件绑定了多个事件处理函数, 在其中一个事件处理函数中调用此方法后将不会继续调用其他的事件处理函数.
stopImmediatePropagation: 'isImmediatePropagationStopped', //是否调用过stopImmediatePropagation方法,
stopPropagation: 'isPropagationStopped' //是否调用过stopPropagation方法
}
//创建事件代理 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 //将preventDefault,stopImmediatePropagatio,stopPropagation方法定义到proxy上
$.each(eventMethods, function(name, predicate) {
proxy[name] = function() {
this[predicate] = returnTrue
return event[name].apply(event, arguments)
}
proxy[predicate] = returnFalse
})
return proxy
} // emulates the 'defaultPrevented' property for browsers that have none
//event.defaultPrevented返回一个布尔值,表明当前事件的默认动作是否被取消,也就是是否执行了 event.preventDefault()方法. function fix(event) {
if (!('defaultPrevented' in event)) {
event.defaultPrevented = false //初始值false
var prevent = event.preventDefault // 引用默认preventDefault
event.preventDefault = function() { //重写preventDefault
this.defaultPrevented = true
prevent.call(this)
}
}
}
//事件委托
$.fn.delegate = function(selector, event, callback) {
return this.each(function(i, element) {
add(element, event, callback, selector, function(fn) {
return function(e) {
//如果事件对象是element里的元素,取与selector相匹配的最近的父元素
var evt, match = $(e.target).closest(selector, element).get(0)
if (match) {
//evt成了一个拥有preventDefault,stopImmediatePropagatio,stopPropagation方法,currentTarge,liveFiredn属性的对象,另也有e的默认属性
evt = $.extend(createProxy(e), {
currentTarget: match,
liveFired: element
})
return fn.apply(match, [evt].concat([].slice.call(arguments, 1)))
}
}
})
})
}
//取消事件委托
$.fn.undelegate = function(selector, event, callback) {
return this.each(function() {
remove(this, event, callback, selector)
})
} $.fn.live = function(event, callback) {
$(document.body).delegate(this.selector, event, callback)
return this
}
$.fn.die = function(event, callback) {
$(document.body).undelegate(this.selector, event, callback)
return this
} //on也有live和事件委托的效果,所以可以只用on来绑定事件
$.fn.on = function(event, selector, callback) {
return !selector || $.isFunction(selector) ?
this.bind(event, selector || callback) : this.delegate(selector, event, callback)
}
$.fn.off = function(event, selector, callback) {
return !selector || $.isFunction(selector) ?
this.unbind(event, selector || callback) : this.undelegate(selector, event, callback)
}
//主动触发事件
// 依据
$.fn.trigger = function(event, data) {
if (typeof event == 'string' || $.isPlainObject(event)) event = $.Event(event)
fix(event)
event.data = data
return this.each(function() {
// items in the collection might not be DOM elements
// (todo: possibly support events on plain old objects)
if ('dispatchEvent' in this) this.dispatchEvent(event)
})
} // 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, data) {
var e, result
this.each(function(i, element) {
e = createProxy(typeof event == 'string' ? $.Event(event) : event)
e.data = data
e.target = element
//遍历元素上绑定的指定类型的事件处理函数集,按顺序执行,如果执行过stopImmediatePropagation,
//那么e.isImmediatePropagationStopped()就会返回true,再外层函数返回false
//注意each里的回调函数指定返回false时,会跳出循环,这样就达到的停止执行回面函数的目的
$.each(findHandlers(element, event.type || event), function(i, handler) {
result = handler.proxy(e)
if (e.isImmediatePropagationStopped()) return false
})
})
return result
} // shortcut methods for `.bind(event, fn)` for each event type
;
('focusin focusout 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 callback ?
//如果有callback回调,则认为它是绑定
this.bind(event, callback) :
//如果没有callback回调,则让它主动触发
this.trigger(event)
}
}) ;
['focus', 'blur'].forEach(function(name) {
$.fn[name] = function(callback) {
if (callback) this.bind(name, callback)
else this.each(function() {
try {
this[name]()
} catch (e) {}
})
return this
}
}) //根据参数创建一个event对象,通过事件模拟的方法
$.Event = function(type, props) {
//当type是个对象时
if (typeof type != 'string') 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, null, null, null, null, null, null, null, null, null, null, null, null)
//添加isDefaultPrevented方法,event.defaultPrevented返回一个布尔值,表明当前事件的默认动作是否被取消,也就是是否执行了 event.preventDefault()方法.
event.isDefaultPrevented = function() {
return this.defaultPrevented
}
return event
} })(Zepto)

//-----------------------20140103

handlers是一个对象,作用是管理zepto的事件。

handlers的key是一个唯一数字,这个唯一数字又对应一个html元素。

表现为{1:[{del: undefined, e: "click",fn: function (){},i: 0,ns: "namespace",proxy: function (e) {},sel: undefined},{del: undefined, e: "click",fn: function (){},i: 1,ns: "namespace",proxy: function (e) {},sel: undefined}], 2:[], 3:[]}。

通过key,可以很容易把一个html元素绑定的所有事件,都解除绑定。

通过ns命名空间,我们可以触发跨类型的事件,比如click和mousedown同时触发。

通过i可以快速找到事件所在数组的位置,可以快速定位。

fn就是事件处理函数。

del是委托封装了fn,用于事件委托。

$.fn.delegate = function(selector, event, callback) {
return this.each(function(i, element) {
add(element, event, callback, selector, function(fn) {
return function(e) {
//如果事件对象是element里的元素,取与selector相匹配的
var evt, match = $(e.target).closest(selector, element).get(0);
if (match) {
//evt成了一个拥有preventDefault,stopImmediatePropagatio,stopPropagation方法,currentTarge,liveFiredn属性的对象,另也有e的默认属性
evt = $.extend(createProxy(e), {
currentTarget: match,
liveFired: element
})
return fn.apply(match, [evt].concat([].slice.call(arguments, 1)))
}
}
})
})
}

到时候执行的回调函数被$.fn.delegate封装过了。当你下次触发事件时,只冒泡到你给定的html元素为止,再判断触发事件来源,然后调用相关事件处理函数。

proxy是封装了fn(或者被委托封装过的fn),可以知道该事件要不要冒泡,阻止默认操作。

sel是zepto对象。

zeptojs库解读2之事件模块的更多相关文章

  1. zeptojs库解读3之ajax模块

    对于ajax,三步骤,第一,创建xhr对象:第二,发送请求:第三,处理响应. 但在编写过程中,实际中会碰到以下问题, 1.超时 2.跨域 3.后退 解决方法: 1.超时 设置定时器,规定的时间内未返回 ...

  2. zeptojs库解读1之整体框架

    首先看的是整体框架, // zepto骨骼,这个函数的作用使得Zepto(slector, context)使用很多$.fn里面的方法 var Zepto = (function(){ // zept ...

  3. Backbone源码解读(一)事件模块

    Backbone源码浅读: 前言: Backbone是早起的js前端MV*框架之一,是一个依赖于underscore和jquery的轻量级框架,虽然underscore中基于字符串拼接的模板引擎相比如 ...

  4. 深入理解nodejs的异步IO与事件模块机制

    node为什么要使用异步I/O 异步I/O的技术方案:轮询技术 node的异步I/O nodejs事件环 一.node为什么要使用异步I/O 异步最先诞生于操作系统的底层,在底层系统中,异步通过信号量 ...

  5. 自己实现一个javascript事件模块

    nodejs中的事件模块 nodejs中有一个events模块,用来给别的函数对象提供绑定事件.触发事件的能力.这个别的函数的对象,我把它叫做事件宿主对象(非权威叫法),其原理是把宿主函数的原型链指向 ...

  6. Backbone事件模块及其用法

    事件模块Backbone.Events在Backbone中占有十分重要的位置,其他模块Model,Collection,View所有事件模块都依赖它.通过继承Events的方法来实现事件的管理,可以说 ...

  7. nodejs事件模块

    nodejs 事件模块 events 只有一个对象 EventEmitter . var EventEmitter = require('events').EventEmitter;var life ...

  8. jQuery源代码学习之九—jQuery事件模块

    jQuery事件系统并没有将事件坚挺函数直接绑定在DOM元素上,而是基于事件缓存模块来管理监听函数的. 二.jQuery事件模块的代码结构 //定义了一些正则 // // //jQuery事件对象 j ...

  9. Backbone事件模块源码分析

    事件模块Backbone.Events在Backbone中占有十分重要的位置,其他模块Model,Collection,View所有事件模块都依赖它.通过继承Events的方法来实现事件的管理,可以说 ...

随机推荐

  1. 005-四种常见的 POST 提交数据方式

    1.http请求方法 HTTP Method RFC Request Has Body Response Has Body Safe Idempotent Cacheable GET RFC 7231 ...

  2. 深度学习之TensorFlow(一)——基本使用

    一.目前主流的深度学习框架Caffe, TensorFlow, MXNet, Torch, Theano比较 库名称 开发语言 速度 灵活性 文档 适合模型 平台 上手难易 Caffe c++/cud ...

  3. 阻止提交按钮的默认 action

    使用 preventDefault() 函数来阻止对表单的提交. 示例代码如下: <html><head><script type="text/javascri ...

  4. [LeetCode] 675. Cut Off Trees for Golf Event_Hard tag: BFS

    You are asked to cut off trees in a forest for a golf event. The forest is represented as a non-nega ...

  5. AdaBoost Classifier和Regressor

    Adaboost原理传送门 AdaBoost在我看理论课程的时候,以分类为例子来讲解的,谁知道sklearn里面基本上都有classifier和regressor两种.这个倒是我没想到的!!! fro ...

  6. ReactNative前端开发者

    ReactNative前端开发者 文档版本0.0.2 Author: Necfol 说明: 本文档用于指导前端React Native的开发,如需开发其他其他框架应用,不适用本文档 前期准备 Reac ...

  7. QPixmap 显示大小

    size picSize(600,400); //将pixmap缩放成picSize大小然后保存在scaledPixmap中 按比例缩放: QPixmap scaledPixmap = pixmap. ...

  8. 3:2 OGNL 简介

    OGNL : (对象图导航语言) 从一个对象到另一个对象 OGNL来源于Xwork: OGNL的作用: OGNL在数据进出值栈的时候进行类型转换

  9. ACM数论之旅10---大组合数-卢卡斯定理(在下卢卡斯,你是我的Master吗?(。-`ω´-) )

    记得前几章的组合数吧 我们学了O(n^2)的做法,加上逆元,我们又会了O(n)的做法 现在来了新问题,如果n和m很大呢, 比如求C(n, m) % p  , n<=1e18,m<=1e18 ...

  10. uva12206 后缀数组

    这题说的是给了一串字符 我们要将这个字符 中找出至少出现m次的最长字符串 一个字符课多次使用 利用后缀数组计算最长的lcp 这里有一个点 记得将后缀数组中加入一个空串 如果遇到全部相同的字符时 没办法 ...