基本概念
事件代理(Event Delegation),又称之为事件委托。是JavaScript中常用绑定事件的常用技巧。顾名思义,“事件代理”即是把原本需要绑定在子元素的响应事件(click、keydown…)委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。
引言
事件委托应用在很多开发场景之中,但是很多同学对委托的原理、特别是对JS原生实现委托不太了解。每每看到此情此景我总觉得“众生皆苦”,正所谓“我不写文章,谁写文章”的普渡心态,是以提供这篇文章解救众生之苦,阿弥陀佛!
释义
在学事件委托时,我们有必要先对事件委托做一个定义。
JS里的事件委托:就是当事件触发时,把要做的事委托给父元素来处理。
再通俗点:就是自己的事不想干,叫它爸爸,甚至爷爷、甚至祖先来干。
作用
在学它的用法和原理之前,我们先了解一下它的作用,有什么好处。再让各位决定是否愿意继续看下去呢?
作用1:节约内存(哇塞,这个好这个棒!)
作用2:能为之后新增的DOM元素依然添加事件(路人甲:这什么鬼?我:死相,真猴急。往后面看就知道了!)
揭开事件委托面纱
场景1:当多个li标签需要添加点击事件时
代码解析:
给5个li标签加了点击事件,当界面上点击li时,会打印它们各自li标签显示的内容。
出现的问题:
此时5个li,看起来每个li的点击事件触发时调用的都是同一个函数,即:
function(){
console.log(this.innerHTML);
};
但其实并不是这样。每个li绑定的都是一个全新的函数,只不过每个函数的样子都一毛一样。
如何验证这个结论呢?很简单,判断每个li标签的onclick是否相等就可以了。
代码验证如下:
至于上面说的函数长得一样,但不是同一个数据这种情况就类似于 var obj1 = {name:"jack",age:16}和var obj2 = {name:"jack",age:16},obj1 == obj2
得到的结果也会是false(如对这一块不理解,下次有空再写一篇文章解答。)
至此,我们可以得到结论,如果有5个li,那么就有5个函数会被创建在内存中占据空间,那如果有100个li呢?就会有100个长相一毛一样的函数在内存中常驻,对内存的开销是巨大的!
解决办法:
利用事件冒泡的原理,把事件加在父元素(ul)身上!
原理解析:
回顾事件冒泡
事件冒泡:即一个元素的事件触发后,会依次一级一级往上调用父级元素的同名事件,直到window(注:IE8和之前的浏览器只到document)
例:div > p > span 当div和p以及span都添加了click事件,当点击span时,会依次向上触发span、p、div的click事件。
效果如下:
O哈哈~(来自抠脚大汉的卖萌符号)
看,这样的形式是不是就相当于把自己的事件,委托在父元素身上处理了呢?因而它才会叫事件委托!
也就是:自己的活不干了,给它爹去干!(画外音:一看就是坑爹的货~)
存在问题及解决方式
思考:如果ul里还有其他子元素例如span,可我只想给li加点击事件,用原来写的事件委托还行吗?
答案是否定的,因为根据事件冒泡原理,所有子元素点击后都会触发父元素的点击,因此,如果你点击了span,也会调用ul的点击事件,这就相当于给span也加了点击事件。这时候该怎么解决呢?
很简单,只要判断一下事件源是不是li就行了,如果是li才执行代码,否则不执行,代码如下:
场景2: 新增元素没有绑定事件的问题
界面描述:
界面上有一个ul里面默认有5个li,并且还有一个按钮,当点击按钮就创建一个新的li,需要不管是默认有的li还是新的li都有点击事件
问题描述:
JS部分代码如下:
此时会发现,页面刚开始加载时就默认存在的5个li是有点击事件的,但是点击按钮创建出来的li没有点击事件。
原因剖析:
因为上面的JS代码是在页面刚加载时执行的,在当时因为不可能去点击按钮,所以能找到的li标签只有默认那5个,因此你打印liList,发现也只有5个
因此,你遍历liList给每个元素加点击事件时,只能给这5个li加到点击事件。因此,如果后面再有li标签,也不拥有点击事件。
使用事件委托解决
把事件只加在ul身上,即可解决,这样内存占用更低,代码也少了很多!效果如图:
我们可以看到,不管是默认有的5个li还是后面才增加的li都有点击事件了
解析:因为事件冒泡机制的存在,不管是原本有的li还是新创建的li,当事件触发时都会一级一级往上调用父元素的同名事件。因此,只要是点击的li标签,都会触发ul的点击事件,所以只要把事件加在ul身上就解决了不管新旧li标签都有点击事件的问题。
jQuery里的事件委托
众所周知,jQuery是JS的一个伟大的第三方库(什么?你还不知道?火星了吧!赶紧恶补!)。JS有的方法,jQuery里都有,并且代码写起来更短。因此事件委托,其实在jQuery里也存在!
jQuery事件委托语法:
$(‘父元素’).on(‘事件名’,‘哪个子元素触发’,传给回调函数的参数,事件触发时的回调函数);
解释:
参数1:事件名,代表要绑定什么事件,但是记得不用加on,也就是说如果你想加点击事件,只要写’click’即可,注意是字符串!所以要打单引号或者双引号
参数2:只能由哪个子元素触发,例如我写 “li”,就代表只能由这个父元素里面的li触发事件,其他子元素不会触发。需要注意的是,这也是字符串,并且,参数2可以不写,那就代表仅仅只是给父元素加一个点击事件,并且所有子元素都能触发到这个事件(因为事件冒泡)
参数3:其实一般不会用,就是如果想事件触发时,自己给回调函数传一些值就写,这个参数也可以不写!
参数4:事件触发时的回调函数
总结:参数1和参数4是必须的,其他是可选的,如果你要用事件委托,请写上参数2
例:
1.on这个方法的参数3可以通过回调函数里的e.data拿到(但一般不会用,大家了解一下有这么个东西即可)
2.在jQuery事件委托的回调函数里this和e.target是同一个东西,但是在JS里this和e.target不是同一个东西
一般参数3不会写,因此代码常见会写成这样:
结语
其实在其他语言里,事件委托会比较复杂,需要创建额外对象。但是由于JS的灵活性,使得在JS里仅仅只需要把事件绑定在父元素即可实现。
事件委托虽然在面试题中略微少见,但是在实际开发中几乎都会用到。因为有时候需要给某一类元素加事件(例如给所有li加点击事件),因为网页内容经常改变,这类元素随时会增加或者减少,为了保证所有这类元素都有事件,也为了节约内存,所以都需要采用事件委托才可实现!