本篇文章参考书籍《JavaScript设计模式》–张容铭
前言
大家有没有听说过事件委托,你可能没有听说过这个名词,但是你应该使用过事件委托,举个例子,各位都用过 <ul> 标签吧,里面会有很多个 <li> 当我们想要点击某个 <li> 的时候,让它的颜色高亮显示。
是不是感觉很熟悉,肯定或多或多少都遇到过这样的需求,那我们的这个时候就不能给 <li> 增加点击事件了,翻译数据贼多,这得加到猴年去。
所以我们的一般做法是给 <ul> 添加点击事件,让 <li> 的事件冒泡到 <ul> 上就可以了。
委托模式
多个对象接收并处理同一请求,他们将请求委托给另一个对象统一处理请求。
前面的例子我们实现一下:
ul.onclick = function(e) {
var e = e || window.event,
tar = e.target || e.srcElement;
if(tar.nodeName.toLowerCase() === 'li') {
tar.style.backgroundColor = 'grey';
}
}
事件委托的原理很简单,但是实现的功能可不止上面这一个,有的时候能帮我“预言未来”。
说的有点玄乎哈,其实呢是处理当前不存在的页面,比如我们会在未来的某个时刻添加某个元素,那想要给未来的元素绑定事件,现在的技术实现起来还是有点困难的,不过事件委托可以巧妙的把绑定事件这件事委托给现有的父元素,便可以实现我们需要的功能了。
<div id="article">
<p>Hello World</p>
</div>
上面给 p 标签增加点击事件很容,到那时如果将来要在 Hello World 下面再加一段话,就得转化一下思路,将事件绑定在父元素 div 上,比如:
var article = focument.getElementById('article');
article.onclick = fucntion() {
var e = e || window.event,
tar = e.target || e.srcElement;
if(tar.nodeName.tolowerCase() === 'p') {
tar.innerHTML = '修改内容!';
}
}
var p = document.createElement('p');
p.innerHTML = '新增一段内容';
article.appendChild(p);
这还没完,委托模式还可以处理一些内存泄漏问题,比如我们的老相识 IE ,由于它引用计数式垃圾回收机制,使得那些对 Dom 元素的引用没有显性清楚的数据就会留在内存中。除非关闭浏览器,否则无法删除,比如:
<div id="btn_container">
<button id="btn">demo</button>
</div>
var g = function(id) {return document.getElementById(id)}
g('btn').onclick = function() {
g('btn_container').innerHTML = '触发了事件';
}
为 id 为 btn 的元素绑定点击事件,在触发时,在其父元素中重置内容,这样将会将按钮自身覆盖掉,然而 g 变量保存的元素绑定的 click 事件没有清除,所以这个事件就会泄漏到内存中。为了解决这一问题,我们可以在父元素设置内容前显性的清除事件事件,比如:
g('btn').onclick = function() {
g('btn') = null;
g('btn_container').innerHTML = '触发了事件';
}
但是 g(‘btn’) = null; 这个清除事件语句在一些标准浏览器中是不需要的,因为他们采用标记清除方式管理自身内存。所以更好的办法就是委托模式了。
g('btn_container').onclick = function(e) {
//获取触发事件元素
var target = e && e.target || window.event.srcElement;
//判断事件触发元素是否是 id 为 btn 的元素
if(target.id === 'btn') {
//重置父元素内容
g('btn_container').innerHTML = '触发了事件';
}
}
事件委托的应用还不止如此,再比如我们向后端发送一些请求时,如果有多个模块都需要发送请求,通常做法是下面:
$.get('./deal.php?g=banner', function(res) {
//处理banner模块逻辑
})
$.get('./deal.php?g=aside', function(res) {
//处理aside模块逻辑
})
$.get('./deal.php?g=article', function(res) {
//处理article模块逻辑
})
$.get('./deal.php?g=member', function(res) {
//处理member模块逻辑
})
$.get('./deal.php?g=message', function(res) {
//处理message模块逻辑
})
这样做我们会向服务器发送 5 次请求,既是对资源的浪费,又会造成漫长的等待,然而更好的方式是将这些请求打包,委托另外一个对象发送,当得到响应数据时,再通过委托对象拆包数据分发给各模块。比如:
var Deal = {
banner: function() {
//处理banner模块逻辑
},
aside: function() {
//处理aside模块逻辑
},
article: function() {
//处理article模块逻辑
},
member: function() {
//处理member模块逻辑
},
message: function() {
//处理message模块逻辑
}
}
$.get('./deal.php?', function(res) {
//数据拆包分发
for(var i in res) {
Deal[i] && Deal[i](res[i]);
}
});
这样我们通过一个请求,将 5 次业务一次性完成,节省时间,又节约开销。