事件委托就是利用事件冒泡机制指定一个事件处理程序,来管理某一类型的所有事件。
即:利用冒泡的原理,把事件加到父级上,触发执行效果。
好处:
事件委托原理我们理解了,现在我们先来理解一下事件流,事件流有事件捕获和事件冒泡的阶段,其实也可以理解为三个阶段,事件捕获-->目标对象事件(target)-->事件冒泡阶段, 用下面这张图来理解清晰明了(网上的图):
1.事件捕获,当一个事件触发后,从window对象触发,不断经过下级节点。在事件到达目标节点之前的过程就是捕获阶段。所有经过的节点都会触发对应的事件。
2.事件冒泡,当事件到达目标节点后,会沿着捕获阶段的路线,原路返回。同样,所有经过的节点,都会触发对应的事件。
W3C标准中的事件添加方法:addEventListener(type,fn,useCapture);中的第三个参数就是指事件是在哪个阶段触发,可选参数,默认为false。true:事件句柄在捕获阶段执行;false:事件句柄在冒泡阶段执行。
我们首先来看一看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元素上才执行事件回调。
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指向当前事件活动的对象(注册该事件的对象,一般为父级),简单来说:currentTarget指的是事件触发后,冒泡到绑定处理程序的元素,就是绑定事件处理程序的元素,target指的是触发事件的元素。
另外,我们在获取目标元素的时候用到了Element.matches方法
语法:
let result = element.matches(selectorString);
2.当然也可以用target.nodeName.toLowerCase() === 'button'来进行获取目标触发元素,以下作为例子结构:
当事件没有冒泡到目标元素的时候就一直循环到找到目标节点,否则什么也不做。
这里我们对比上面原生JS实现事件委托的两种方式的区别,除了匹配元素使用的 matches 方法不一样,我们第二种方式还用到了 while 循环去寻找目标对象,对于匹配使用的无论是 nodeName 还是 matches 并没有多大区别只是匹配目标元素(IE不支持matches 方法)。
事件委托用不用 while 循环去寻找取决于你的 html 结构,如果你的目标元素就是最底层(即点击的时候就是 ev.target 指向的元素)那么就不需要使用 while 循环查找。例如以下结构你的目标元素是 li,li 里面不再有元素节点。
- 我是li1
- 我是li2
- 我是li3
另一种,如果你的目标元素中还有元素节点,但是你的目标仍然是 li,这个时候就需要使用 while + parentNode 去查找目标节点:
- 我是li1
- 我是li2
- 我是li3