事件委托优点
- 减少DOM操作的,减少浏览器的重绘(repaint)和重排(reflow),从而提高性能;
- 减少内存空间的占用率,因为每一个函数都是一个对象,对象越多,内存占有率就越大,自然性能就越差,使用事件委托,只需要在其父元素中定义一个事件就可以。
- 适合事件委托的事件有:click,mousedown,mouseup,keydown,keyup,keypress
- 可以方便地动态添加和修改元素,不需要因为元素的改动而修改事件绑定。
为什么要用事件委托
dom需要有事件处理程序,我们都会直接给它设事件处理,那如果是很多的dom需要添加事件处理?比如我们有100个li,每个li都有相同的click点击事件,可能我们会用for循环的方法,来遍历所有的li,然后给它们添加事件,那这么做会存在什么影响呢?
- 操作DOM次数过多,造成浏览器的重排和重绘就越多;
- 每个事件都是一个对象,事件处理程序越多,占用的内存越多,影响前端性能;
事件委托(事件代理)原理——事件冒泡
事件委托利用事件冒泡(从最深的节点开始,然后逐步向上传播事件)只在他们的父元素上指定一个事件处理程序,就可以管理某一类型的的所有事件。
事件委托的实现
事件源:Event对象提供了一个属性叫target,可以返回事件的目标节点,标准浏览器用ev.target,IE浏览器用event.srcElement,
1,父元素绑定事件
<div id="box">
<input type="button" id="add" value="添加" />
<input type="button" id="remove" value="删除" />
<input type="button" id="move" value="移动" />
<input type="button" id="select" value="选择" />
</div>
window.onload = function(){
var oBox = document.getElementById("box");
oBox.onclick = function (ev) {
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLocaleLowerCase() == 'input'){
switch(target.id){
case 'add' :
alert('添加');
break;
case 'remove' :
alert('删除');
break;
case 'move' :
alert('移动');
break;
case 'select' :
alert('选择');
break;
}
}
}
}
//或者使用oBox.addEventListener(时间类型,函数,默认false为时间冒泡),下面我会讲到
上述target只是获得了当前节点位置,但不知道具体节点名称,所以使用nodeName来获取标签名,这里获得但标签名是大写INPUT
除来获取标签名,这里也可以获取当前点击标签但其他属性,例如:ev.target.id, ev.target.value, ev.target.type.
这里用父级div做事件处理,当input被点击时,由于冒泡原理,事件就会冒泡到div上,因为div上有点击事件,所以事件就会触发
接下来我们再看一个例子 addEventListener
<div id="div1">
点击
</div>
<script>
window.onload = function(){
let div1 = document.getElementById('div1');
div1.onclick = function(){
console.log('打印第一次')
}
div1.onclick = function(){
console.log('打印第二次')
}
}
</script>
可以看到第二个点击注册事件覆盖了第一个注册事件,只执行了console.log('打印第二次');
用addEventListener(type,listener,useCapture)实现
- type: 必须,String类型,事件类型
- listener: 必须,函数体或者JS方法
- useCapture: 可选,boolean类型。指定事件是否发生在捕获阶段。默认为false,事件发生在冒泡阶段
window.onload = function(){
let div1 = document.getElementById('div1');
div1.addEventListener('click',function(){
console.log('打印第一次')
})
div1.addEventListener('click',function(){
console.log('打印第二次')
})
}
我们看一下效果
可以看到两个注册事件都成功触发了。 useCapture是事件委托的关键。
事件捕获和事件冒泡机制
事件捕获:当一个事件触发后,从Window对象触发,不断经过下级节点,直到目标节点。在事件到达目标节点之前的过程就是捕获阶段。所有经过的节点,都会触发对应的事件
事件冒泡:当事件到达目标节点后,会沿着捕获阶段的路线原路返回。同样,所有经过的节点,都会触发对应的事件
例子:假设有body和body节点下的div1均有绑定了一个注册事件.
效果:
当为事件捕获(useCapture:true)时,先执行body的事件,再执行div的事件
当为事件冒泡(useCapture:false)时,先执行div的事件,再执行body的事件
//当useCapture为默认false时,为事件冒泡
<body>
<div id="div1"></div>
</body>
<script>
window.onload = function(){
let body = document.querySelector('body');
let div1 = document.getElementById('div1');
body.addEventListener('click',function(){
console.log('打印body')
})
div1.addEventListener('click',function(){
console.log('打印div1')
})
}
</script>
//结果:打印div1 打印body
//当useCapture为true时,为事件捕获
<body>
<div id="div1"></div>
</body>
<script>
window.onload = function(){
let body = document.querySelector('body');
let div1 = document.getElementById('div1');
body.addEventListener('click',function(){
console.log('打印body')
},true)
div1.addEventListener('click',function(){
console.log('打印div1')
})
}
</script>
//结果:打印body 打印div1
接下来我们看一下事件委托和新增节点绑定事件之间的关系
<body>
<div id="div">
<div class="div1">div1</div>
<div class="div2">div2</div>
</div>
</body>
<script>
window.onload = function(){
let div = document.getElementById('div');
div.addEventListener('click',function(e){
console.log(e.target)
})
let div3 = document.createElement('div');
div3.setAttribute('class','div3')
div3.innerHTML = 'div3';
div.appendChild(div3)
}
</script>
依次点击子div:
虽然没有给div1和div2添加点击事件,但是无论是点击div1还是div2,都会打印当前节点。因为其父级绑定了点击事件,点击div1后冒泡上去的时候,执行父级的事件。
这样无论后代新增了多少个节点,一样具有这个点击事件的功能。