事件委托

我们都知道jquery的on是采用的事件委托,但是真正了解什么是事件委托仍然要花一定功夫,于是我们这里来试试。
闭包是事件委托实现的基石,我们就以事件委托深入学习下闭包。

html结构:

1
2
3
4
5
6
<input id="input" value="input" type="button" />
<div id="div">我是div</div>
<span id="span">我是span</span>
<div id="wrapper">
<input id="inner" value="我是inner" type="button" />
</div>

事件委托原理

首先事件委托实现的基石是事件冒泡,我们在页面的每次点击最终都会冒泡到其父元素,所以我们在document处可以捕捉到所有的事件。
知道了这个问题后,我们可以自己实现一个简单的delegate事件绑定方式:

1
2
3
4
5
6
function delegate(selector, type, fn){
document.addEventListener(type,fn,false);
}
delegate('#input','click',function(){
console.log('ttt');
});

这段代码是最简单的实现,首先我们无论点击页面什么地方都会执行click事件,当然这显然不是我们要看到的情况,于是我们做处理,让每次点击时候触发应有的事件。
这里有几个问题比较尖锐:
1.既然我们事件是绑定到document上面,那么我怎么知道我现在是点击的什么元素呢。
2.就算我能根据e.target获取当前点击元素,但是我怎么知道是哪个元素具有事件呢
3.就算我能根据selector确定当前点击的哪个元素需要执行事件,但是我怎么找得到是哪个事件呢

如果以上问题能解决的话,我们后面的流程就比较简单了。
确定点击元素是否触发事件
首先,我们点击时候可以使用e.target获取当前点击元素,然后根据selector依次寻找其父DOM,如果找得到就应该触发事件,因为这些都是要在触发时候才能决定,所以我们需要重写其fn回调函数,于是简单操作后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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, el));
callback.apply(selectorEl,evt);
var s = '';
}
var s = '';
};
document.addEventListener(type,handler,false);
}

调用函数:

1
2
3
delegate('#input','click',function(){
console.log('input');
});

我们这里来简单解析下整个程序
1.我们调用delegate为body增加事件
2.在具体绑定时候,我们将其中的回调给重写了
3.在具体点击时候(绑定几次事件实际就会触发几次click),会获取当前元素,查看其选择器搜索的元素是否包含他,如果包含的话便触发事件
4.由于这里每次注册时候都会形成一个闭包,传入的callback被维护起来了,所以每次调用便能找到自己的回调函数
5.最后重写event句柄的currentTarget,于是一次事件委托就结束了

ps:这里实现还有问题,比如在even处理上,可去查看在zepto的实现方法

事件委托问题
事件委托可以提高效率但是有一个比较烦的事情就是阻止冒泡没用
拿上面的代码来说,有一个inner元素和一个wrapper元素,它们是互相包裹关系
但是其执行顺序并不是先内再外的事件冒泡顺序,因为事件全部绑定到了document上面,所以这里执行顺序便是以其注册顺序所决定
这里有一个问题便是如何“阻止冒泡”
在inner处完了执行

1
e.stopImmediatePropagation()

是可以达到目的的,但是仍然要求inner元素必须注册到之前


参考:
工作中的”闭包“与事件委托的”阻止冒泡“