委托模式
委托模式:多个对象接受并处理同一请求,他们将请求委托给另外一个对象统一处理请求。
事件委托
我们都知道完整的事件流是从事件捕获开始,到触发该事件,再到事件冒泡三个阶段。
运用场景
比如我们有一个列表,是ul>li这样的结构,如果我们需要给每个li绑定事件的话,然后执行某些业务逻辑的话,我们可能第一个想的就是循环遍历li,然后逐一绑定事件,比如:
// 获取ul下的所有li
const lis = docuemnt.querySelectorAll('ul>li')
// 遍历所有li逐一绑定事件
for(let i=0,_len = lis.length;i<_len;i++){
lis[i].onclick = function(e){....}
}
这样做功能上是没有问题的,但是当如果这个li列表很长的时候,可能会造成不必要的性能浪费,因为事件多了,内存的消耗就大了。这时我们就可以用上我们要讲的委托模式了。
利用事件委托解决列表的事件绑定
我们只需要为父元素(这里指的是ul)绑定一个事件,将子元素的事件委托给父元素,然后利用事件冒泡传递,然后通过判断事件源的某种特性来执行你的业务逻辑。
// 获取ul
const ul = docuemnt.querySelector('ul')
ul.onclick = function(e){
// 考虑一些兼容性问题
const e = e || window.event,
tar = e.target || e.srcElement
// 当点击的目标源的标签名是li时
if(tar.nodeName.toLowerCase() === 'li'){
// 处理业务逻辑
}
}
"预见未来"
预见未来指的是未来的事情,也就是当前页面所不存在的,比如我们在未来某一时刻会添加某些元素,要是想对未来的元素也绑定事件,那么就可以使用委托模式巧妙地将未来元素的事件委托给现有的父元素,就可以实现对未来元素的间接事件绑定。
比如还是以上面那个例子来演示事件委托的"预见未来"性:
// 如下DOM结构
<ul>
<li>li1</li>
<li>li2</li>
</ul>
<script>
// 页面加载完毕
window.onload= function (){
const ul = document.querySelector('ul')
// 创建新的li
const li = document.createElement('li')
// 设置文本
li.textContext = '我是"未来添加"的li'
// 插入到ul中
ul.appendChild(li)
}
// 获取ul下的所有li
const lis = docuemnt.querySelectorAll('ul>li')
// 遍历所有li逐一绑定事件
for(let i=0,_len = lis.length;i<_len;i++){
lis[i].onclick = function(e){
console.log('处理业务逻辑!')
}
}
</script>
此时,你会发现后面新增的li并没有绑定上click事件,我们就可以通过事件委托来实现(上面已经实现过了,这里就不再复述了),而对于可能"未来"会出现的元素,也能绑定上事件,这就是事件委托的强大之处了。
内存泄露
有时候委托模式不仅能在性能上有一定的优越性,它还能处理一些内存泄露问题,是因为老版本的IE浏览器,由于它的引用计数式垃圾回收机制,使得那些DOM元素的引用没有显性清除的数据会遗留在内存中,除非关闭浏览器,否则无法清除。比如以下:
<div id="contaniner">
<button id="btn">Test</button>
</div>
// js部分
const G = function(id)={return document.getElementById(id)}
G('btn').onclick = function(){
G('contaniner').innerHTML = '触发了'
}
为id为btn的元素绑定点击事件,在触发时,在其父元素中重置了内容,这样将会将按钮自身覆盖掉,然而G变量保存的元素绑定的click事件并没有被清除,所以这个事件就会泄露到内存中。为了解决这个问题,我们可以在父元素设置内容前显性地清清除事件。比如:
G('btn').onclick = function(){
// 手动清除btn绑定的事件
G('btn').onclick = null
G('contaniner').innerHTML = '触发了'
}
但是对于其他一些使用标记清除方式的垃圾回收机制的浏览器并不需要我们手动去清除。所以更好的解决方法是采用委托模式了。
G('contaniner').onclick = function(e){
const tar = e.target || window.event.srcElement
if(tar.id === 'btn'){
// 重置父元素内容
G('contaniner').innerHTML = '触发了'
}
}
总结
委托模式优化了页面中的事件绑定,对于未来元素的事件绑定是现有技术所做不到的,所以也可以使用委托模式来实现。
练习
用委托模式封装一个事件委托方法,事件委托方法使用如下:
// 使用方法
delegate(document.body,'button','click',function(){
console.log('委托成功!')
})
// 实现
function delegate(container,target,type,cb){
container['on'+type] = function(e){
e = e || window.event
const tar = e.target || e.scrElement
if(tar.nodeName.toLowerCase() === target){
cb && cb(e)
}
}
}
参考:JavaScript设计模式