引言:
事件委托应用在很多开发场景之中,但是很多同学对委托的原理、特别是对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事件。
代码如下:
效果如下:
其中,在每个触发的事件里,通过事件对象.target能拿到 触发事件的源头元素 也就是 事件源。
因此,在上述代码中,改成
可发现,触发事件时,打印出来的e.target都是span,如下:
注:事件对象在ie8中要通过window.event才能取到,因此e = e || window.event是做兼容处理(详细了解请翻阅黑马程序员前端课程关于事件对象的讲解)
解释委托原理
在回顾完事件冒泡后,我们显而易见得到结论:给所有li添加点击事件,只要加到它们的父元素ul身上的根本原因是利用了事件冒泡。也即:无论点击哪个li,都会自动触发ul的点击事件,然后在ul里通过e.target能获得真正被点击的那个li,继而拿到它的innerHTML
小结:如果给一堆元素加事件,并且事件触发时执行的代码都差不多时,就可以把事件加在父元素身上啦!这样可以更节省内存空间哦!O(∩_∩)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加点击事件),因为网页内容经常改变,这类元素随时会增加或者减少,为了保证所有这类元素都有事件,也为了节约内存,所以都需要采用事件委托才可实现!