什么是事件委托
关于这个解释我有看到一个很贴切的例子:
有三个同事预计会在周一收到快递。为签收快递,有两种办法:一是三个人在公司门口等快递;二是委托给前台小姐姐代为签收。现实当中,我们大都采用委托的方案(公司也不会容忍那么多员工站在门口就为了等快递)。前台小姐姐收到快递后,她会判断收件人是谁,然后按照收件人的要求签收,甚至代为付款。这种方案还有一个优势,那就是即使公司里来了新员工(不管多少),前台小姐姐也会在收到寄给新员工的快递后核实并代为签收。 这里其实还有2层意思的:
第一,现在委托前台的小姐姐是可以代为签收的,即程序中的现有的dom节点是有事件的;
第二,新员工也是可以被前台小姐姐代为签收的,即程序中新添加的dom节点也是有事件的
这时候我们就懂这个委托的基本概念了吧。
为什么要用事件委托呢
一般来说,dom需要有事件处理程序,我们都会直接给它设事件处理程序就好了,那如果是很多的dom需要添加事件处理呢?比如我们有100个li,每个li都有相同的click点击事件,可能我们会用for循环的方法,来遍历所有的li,然后给它们添加事件,那这么做会存在什么影响呢?
1.操作DOM次数过多,造成浏览器的重排和重绘就越多;
2.每个事件都是一个对象,事件处理程序越多,占用的内存越多,影响前端性能;
在JavaScript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能,因为需要不断的与dom节点进行交互,访问dom的次数越多,引起浏览器重绘与重排的次数也就越多,就会延长整个页面的交互就绪时间,这就是为什么性能优化的主要思想之一就是减少DOM操作的原因;如果要用事件委托,就会将所有的操作放到js程序里面,与dom的操作就只需要交互一次,这样就能大大的减少与dom的交互次数,提高性能;
每个函数都是一个对象,是对象就会占用内存,对象越多,内存占用率就越大,自然性能就越差了,比如上面的100个li,就要占用100个内存空间,如果是1000个,10000个呢,如果用事件委托,那么我们就可以只对它的父级(如果只有一个父级)这一个对象进行操作,这样我们就需要一个内存空间就够了,是不是省了很多,自然性能就会更好。
事件委托的原理
事件委托是利用事件的冒泡原理来实现的,何为事件冒泡呢?就是事件从最深的节点开始,然后逐步向上传播事件
如上图所示,事件模型是指分为三个阶段:
- 捕获阶段:在事件冒泡的模型中,捕获阶段不会响应任何事件;
- 冒泡阶段:冒泡阶段就是事件的触发响应会从最底层目标一层层地向外到* 最外层(根节点),事件代理即是利用事件冒泡的机制把里层所需要响应 的事件绑定到外层
jQuery中的事件委托原理
$.on: 基本用法:
$('.parent').on('click', 'a', function () { console.log('click event on tag a'); }),
它是 .parent 元素之下的 a 元素的事件代理到 $('.parent') 之上,只要在这个元素上有点击事件,就会自动寻找到 .parent 元素下的 a 元素,然后响应事件;
$.delegate: 基本用法:
$('.parent').delegate('a', 'click', function () { console.log('click event on tag a'); }),
同上,并且还有相对应的 $.delegate 来删除代理的事件;
实现功能
给一百个按钮添加点击事件,等冒泡的时候判断target是不是这100个按钮中的一个
div1.addEventListener('click',(e)=>{
const t = e.target
if(t.target.toLowerCase()==='button'){
console.log('button 被点击了')
console.log('button data-id是'+t.dataset.id)
}
})
监听不存在的元素点击事件
setTimeout(()=>{
const button = document.createElement('button')
button.textContent = 'click 1'
div1.appendChild(button)
},1000)
div1.addEventListener('click',(e)=>{
const t = e.target
if(t.targetName.toLowerCase()==='button'){
console.log('button 被点击了')
}
})
封装事件委托
一百个按钮添加点击事件,点击#Div1里的span元素时,调用fn函数
setTimeout(()=>{
const button = document.createElement('button')
const span = document.createElement('span')
span.textContent = 'click 1'
button.appendChild(span)
div1.appendChild(button)
},1000)
on( 'click','#div1','button',() => {
console.log('button 被点击了')
})
function on(eventType,element, selector, fn) {
if(!(element instanceof Element)){
element = document.querySelector(element)
}
element.addEventListener(eventType, e => {
let el = e.target
while (!el.matches(selector)) {
if (element === el) {
el = null
break
}
el = el.parentNode
}
el && fn.call(el, e, el)
})
return element
}