1.原理:
事件委托就是利用事件冒泡机制指定一个事件处理程序,来管理某一类型的所有事件。
即:利用冒泡的原理,把事件加到父级上,触发执行效果。
好处:
- 只在内存中开辟了一块空间,节省资源同时减少了DOM操作,提供性能
- 对于新添加的元素也会有之前的事件原
事件委托原理我们理解了,现在我们先来理解一下事件流,事件流有事件捕获和事件冒泡的阶段,其实也可以理解为三个阶段,事件捕获-->目标对象事件(target)-->事件冒泡阶段, 用下面这张图来理解清晰明了(网上的图):
1.事件捕获,当一个事件触发后,从window对象触发,不断经过下级节点。在事件到达目标节点之前的过程就是捕获阶段。所有经过的节点都会触发对应的事件。
2.事件冒泡,当事件到达目标节点后,会沿着捕获阶段的路线,原路返回。同样,所有经过的节点,都会触发对应的事件。
W3C标准中的事件添加方法:addEventListener(type,fn,useCapture);中的第三个参数就是指事件是在哪个阶段触发,可选参数,默认为false。true:事件句柄在捕获阶段执行;false:事件句柄在冒泡阶段执行。
2.JQ的事件委托实现
我们首先来看一看jQuery的事件委托是怎么做的。
jq有个on添加事件的方法,也可以做事件委托。使用 on() 方法添加的事件处理程序适用于当前及未来的元素(比如由脚本创建的新元素)。
使用:
$(selector).on(event,childSelector,data,function)
参数 说明:
参数 | 描述 |
---|---|
event | 必需。规定要从被选元素移除的一个或多个事件或命名空间。 由空格分隔多个事件值,也可以是数组。必须是有效的事件。 |
childSelector | 可选。规定只能添加到指定的子元素上的事件处理程序(且不是选择器本身,比如已废弃的 delegate() 方法)。 |
data | 可选。规定传递到函数的额外数据。 |
function | 可选。规定当事件发生时运行的函数。 |
重点是上面的参数可以指定为该事件使用的事件代理,就是代理子元素事件的父级节点。childSelector就是目标元素,即点击了这个childSelector元素,触发事件,但是什么时候处理执行事件方法呢,是事件冒泡到selector节点的时候执行事件回调函数。
举个栗子:
$('body').on('click','.btns',function(){
//do something
console.log(123);
})
上面的栗子是说,我页面上所有现有的或者动态新添加的含有btn类的元素的点击事件,都委托给body元素处理,即点击含有btn类的元素要到事件捕获再冒泡到body元素上才执行事件回调。
3.原生JS的实现
1.我们可以用原生的JS封装一个事件绑定并具有事件委托功能的函数。如下:
function bindEvent(elem,type,selector,fn){
//当只有三个参数时我们一般第三个参数是fn,则没有事件委托机制,需要把fn赋值为第三个参数
if(!fn){
fn = selector;
selector = null;
}
elem.addEventListener(type,function(ev){
if(selector){
//如果使用了事件委托,需要匹配到目标元素
let target = ev.target;
if(target.matches(selector)){
fn.call(target,ev);
}
}else{
//普通绑定事件直接调用fn
fn(ev);
}
})
}
target 和currentTarget的区别:
- target在事件流的目标阶段,一个触发事件的对象的引用。
- currentTarget当事件遍历DOM时,标识事件的当前目标。它总是引用事件处理程序附加到的元素,而不是event.target,它标识事件发生的元素。
只有当事件流处于目标阶段的时候,他们两个的指向才是一致的,而处于捕获和冒泡阶段的时候,target指向被单击的对象,而currentTarget指向当前事件活动的对象(注册该事件的对象,一般为父级),简单来说:currentTarget指的是事件触发后,冒泡到绑定处理程序的元素,就是绑定事件处理程序的元素,target指的是触发事件的元素。
另外,我们在获取目标元素的时候用到了Element.matches方法
语法:
let result = element.matches(selectorString);
- result值为true或者false
- selectorString是个css选择器
2.当然也可以用target.nodeName.toLowerCase() === 'button'来进行获取目标触发元素,以下作为例子结构:
<body>
<div class="box">
<div class="box2">
<div class="box3">
<button id="btn"><a>button</a></button>
</div>
</div>
</div>
<script>
let elem = document.querySelector('.box');
elem.addEventListener('click',function(ev){
let target = ev.target;
while(!target.classList.contains('box')){
if(target.nodeName.toLowerCase()==='button'){
//do something
break;
}
target = target.parentNode;
}
})
</script>
</body>
当事件没有冒泡到目标元素的时候就一直循环到找到目标节点,否则什么也不做。
这里我们对比上面原生JS实现事件委托的两种方式的区别,除了匹配元素使用的 matches 方法不一样,我们第二种方式还用到了 while 循环去寻找目标对象,对于匹配使用的无论是 nodeName 还是 matches 并没有多大区别只是匹配目标元素(IE不支持matches 方法)。
事件委托用不用 while 循环去寻找取决于你的 html 结构,如果你的目标元素就是最底层(即点击的时候就是 ev.target 指向的元素)那么就不需要使用 while 循环查找。例如以下结构你的目标元素是 li,li 里面不再有元素节点。
<div>
<ul>
<li>我是li1</li>
<li>我是li2</li>
<li>我是li3</li>
</ul>
</div>
另一种,如果你的目标元素中还有元素节点,但是你的目标仍然是 li,这个时候就需要使用 while + parentNode 去查找目标节点:
<div>
<ul>
<li><span>我是li1</span></li>
<li><span>我是li2</span></li>
<li><span>我是li3</span></li>
</ul>
</div>