这段代码是最简单的实现,首先我们无论点击页面什么地方都会执行click事件,当然这显然不是我们想要看到的情况,于是我们做处理,让每次点击时候触发他应有的事件
这里有几个问题比较尖锐:
① 既然我们事件是绑定到document上面,那么我怎么知道我现在是点击的什么元素呢
② 就算我能根据e.target获取当前点击元素,但是我怎么知道是哪个元素具有事件呢
③ 就算我能根据selector确定当前点击的哪个元素需要执行事件,但是我怎么找得到是哪个事件呢
如果能解决以上问题的话,我们后面的流程就比较简单了
确定点击元素是否触发事件
首先,我们点击时候可以使用e.target获取当前点击元素,然后再根据selector依次寻找其父DOM,如果找得到就应该触发事件
因为这些都是要在触发时候才能决定,所以我们需要重写其fn回调函数,于是简单操作后:
var arr = []; var slice = arr.slice; var extend = function (src, obj) { var o = {}; for (var k in src) { o[k] = src[k]; } for (var k in obj) { o[k] = obj[k]; } return o; }; function delegate(selector, type, fn) { var callback = fn; var handler = function (e) { //选择器找到的元素 var selectorEl = document.querySelector(selector); //当前点击元素 var el = e.target; //确定选择器找到的元素是否包含当前点击元素,如果包含就应该触发事件 /************* 注意,此处只是简单实现,实际应用会有许多判断 *************/ if (selectorEl.contains(el)) { var evt = extend(e, { currentTarget: selectorEl }); evt = [evt].concat(slice.call(arguments, 1)); callback.apply(selectorEl, evt); var s = ''; } var s = ''; }; document.addEventListener(type, handler, false); }
于是我们可以展开调用了:
delegate('#input', 'click', function () { console.log('input'); }); delegate('#div', 'click', function () { console.log('div'); }); delegate('#wrapper', 'click', function () { console.log('wrapper'); }); delegate('#span', 'click', function () { console.log('span'); }); delegate('#inner', 'click', function () { console.log('inner'); });
我们这里来简单解析下整个程序
① 我们调用delegate为body增加事件
② 在具体绑定时候,我们将其中的回调给重写了
③ 在具体点击时候(绑定几次事件实际就会触发几次click),会获取当前元素,查看其选择器搜索的元素是否包含他,如果包含的话便触发事件
④ 由于这里每次注册时候都会形成一个闭包,传入的callback被维护起来了,所以每次调用便能找到自己的回调函数(这里对闭包理解很有帮助)
⑤ 最后重写event句柄的currentTarget,于是一次事件委托就结束了
PS:我这里实现还有问题的,比如在event的处理上就有问题,但是作为demo的话我便不去关注了,有兴趣的朋友自己去看zepto实现吧
事件委托的问题
事件委托可以提高效率但是有一个比较烦的事情就是阻止冒泡没用
拿上面代码来说,有一个inner元素和一个wrapper元素,他们是互相包裹关系
但是其执行顺序并不是先内再外的事件冒泡顺序,因为事件全部绑定到了document上面,所以这里执行顺序便是以其注册顺序所决定
这里有一个问题便是如何“阻止冒泡”
在inner处完了执行
e.stopImmediatePropagation()
是可以达到目的的,但是仍然要求inner元素必须注册到之前
除此之外,就只给这种会嵌套的元素绑定一个事件,又e.target决定到底执行哪个事件,具体各位自己斟酌
以上问题在使用backbone可能实际会遇到
您可能感兴趣的文章: