了解事件委托之前请仔细看封面大图中的泡泡是怎么变化的
事件委托
事件委托就是子级将事件委托给父级,开始监听,如果触发,父级通过ev.target来获取单击的哪个目标,从而弹出目标
为什么要用事件委托:
一般来说,dom需要有事件处理程序,我们都会直接给它设事件处理程序就好了,那如果是很多的dom需要添加事件处理呢?比如我们有100个li,每个li都有相同的click点击事件,可能我们会用for循环的方法,来遍历所有的li,然后给它们添加事件,那这么做会存在什么影响呢?
在Js中,添加到页面上的事件数将直接关系页面的整体运行性能,因为需要不断的与dom节点进行交互,访问dom的次数越多,引起浏览器重绘与重排的次数也就越多,就会延长整个页面的交互时间,这就是为什么性能优化的主要思想之一就是减少DOM操作的原因.如果要用事件委托,就会将所有的操作放到js程序里面,与dom的操作就只需要交互一次,这样就能大大的减少与dom的交互次数,提高性能.
每个函数都是一个对象,是对象就会占用内存,对象越多,内存占用率就越大,自然性能就越差了(内存不够了是硬伤),比如上面的100个li,就要占用100个内存空间,如果是1000个,10000个呢,那只能说GG了,如果用事件委托,那么我们就可以只对它的父级(如果只有一个父级)这一个对象进行操作,这样我们就需要一个内存空间就够了,是不是省了很多,100个内存空间和1个内存空间你觉得哪个更好呢。
事件委托的原理:
事件委托是利用事件的冒泡原理来实现的,就是事件从最深的节点开始,然后逐步向上传播事件,举个例子:页面上有这么一个节点树,div>body;比如给最里面的a(假设此时div中新增了ul,li,a标签)加一个click点击事件,那么这个事件就会一层一层的往外执行,执行顺序d>li>ul>div,有这样一个机制,那么我们给最外面的div加点击事件,那么里面的ul,li,a做点击事件的时候,都会冒泡到最外层的div上,所以都会触发,这就是事件委托,委托它们父级代为执行事件。
阻止冒泡
w3c是e.stopPropagation(),IE则是e.cancelBubble = true
HTML
<input type="button" name="" id="btn" value="添加" />
<ul id="ul1">
<li>111</li>
<li>222</li>
<li>333</li>
<li>444</li>
</ul>
JS
window.onload = function(){
var oBtn = document.getElementById("btn");
var oUl = document.getElementById("ul1");
var aLi = oUl.getElementsByTagName('li');
var num = 4;
//事件委托,添加的子元素也有事件
oUl.onmouseover = function(ev){
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLowerCase() == 'li'){
target.style.background = "red";
}
};
oUl.onmouseout = function(ev){
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLowerCase() == 'li'){
target.style.background = "#fff";
}
};
//添加新节点
oBtn.onclick = function(){
num++;
var oLi = document.createElement('li');
oLi.innerHTML = 111*num;
oUl.appendChild(oLi);
};
}
上面是用事件委托的方式,新添加的子元素是带有事件效果的,我们可以发现,当用事件委托的时候,根本就不需要去遍历元素的子节点,只需要给父级元素添加事件就好了,其他的都是在js里面的执行,这样可以大大的减少dom操作,这才是事件委托的精髓所在。
现在给一个场景 ul > li > div > p,div占满li,p占div,还是给ul绑定时间,需要判断点击的是不是li(假设li里面的结构是不固定的),那么e.target就可能是p,也有可能是div,这种情况你会怎么处理呢?
<ul id="test">
<li>
<p>11111111111</p>
</li>
<li>
<div>
22222222
</div>
</li>
<li>
<span>3333333333</span>
</li>
<li>4444444</li>
</ul>
上列表,有4个li,里面的内容各不相同,点击li,event对象肯定是当前点击的对象,怎么指定到li上,下面我直接给解决方案:
var oUl = document.getElementById('test');
oUl.addEventListener('click',function(ev){
var target = ev.target;
while(target !== oUl ){
if(target.tagName.toLowerCase() == 'li'){
console.log('li click~');
break;
}
target = target.parentNode;
}
})
核心代码是while循环部分,实际上就是一个递归调用,你也可以写成一个函数,用递归的方法来调用,同时用到冒泡的原理,从里往外冒泡,知道currentTarget为止,当当前的target是li的时候,就可以执行对应的事件了,然后终止循环。
页面处理大型数据时
当页面遇到大型数据时,可根据监听window.addEventListener和window.requestAnimationframe()来处理数据
具体可借鉴一下传送门
如上述文章中有不对的地方,可提取出来,及时发于评论我将积极改正。