1.js事件委托是什么
js里的事件委托,简而言之,就是将原本应该在当前元素绑定的事件,放到它的祖先元素上,让祖先元素来委托处理。
再通俗点:就是自己的事不想干,叫它爸爸,甚至爷爷、甚至祖先来干。
真正绑定事件的是外层元素,当事件响应到需要绑定的元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然后在外层元素上去执行函数。
js事件委托作用:
1).节约内存,减少内存消耗
试想一下,如果我们有一个列表,列表之中有大量的列表项,我们需要在点击列表项的时候响应一个事件。如果给每个列表项一一都绑定一个函数,那对于内存消耗是非常大的,效率上需要消耗很多性能。因此,比较好的方法就是把这个点击事件绑定到他的父层,然后在执行事件的时候再去匹配判断目标元素。所以事件委托可以减少大量的内存消耗,节约效率。
2).动态绑定事件,能为之后新增的DOM元素依然添加事件(如:js动态添加li)
比如上述的例子中列表项就几个,我们给每个列表项都绑定了事件。在很多时候,我们需要通过 AJAX 或者用户操作动态的增加或者去除列表项元素,那么在每一次改变的时候都需要重新给新增的元素绑定事件,给即将删去的元素解绑事件。如果用了事件委托就没有这种麻烦了,因为事件是绑定在父层的,和目标元素的增减是没有关系的,执行到目标元素是在真正响应执行事件函数的过程中去匹配的。所以使用事件在动态绑定事件的情况下是可以减少很多重复工作的。
前面提到 DOM 中事件委托的实现是利用事件冒泡的机制,那么事件冒泡是什么呢?
2.事件流或事件传播
事件流指从页面中接收事件的顺序,也可理解为事件在页面中传播的顺序。
事件的传播分为三个阶段:捕获阶段、目标阶段、冒泡阶段。
1).捕获阶段:从window,document 和根元素开始,事件向下扩散至目标元素的祖先
2).目标阶段:事件在用户单击的元素上触发
3).冒泡阶段:最后,事件冒泡通过目标元素的祖先,一直到根元素 document 和 window。
我们通常用 addEventListener 给元素添加事件。
document.querySelector('#card')addEventListener(
'click',
function (event) {
console.log('div#card 冒泡点击', event);
},
false
);
第一个参数是事件名。
第二个参数是事件响应函数,可以拿到当前的事件对象。
第三个参数是可选的,表示监听的是否为捕获阶段,由addEventListener(ev, fn, useCapture = false)
第三个参数控制,默认值false
,当useCapture
为false
,表示该事件在冒泡阶段触发,当useCapture
为true
,表示该事件在捕获阶段触发。
调用事件对象的 event.composedPath()
方法可以拿到事件路径的顺序。
假设我们的 DOM 结构如下:
<html>
<head>
<title>测试</title>
<meta charset="UTF-8" />
</head>
<body>
<div id="app">
<div id="box-1">
<div id="card">card</div>
</div>
<div id="box-2"></div>
</div>
</body>
</html>
现在我们点击 card 文字时,DOM 就会产生事件流。
事件流首先会进入 捕获阶段,从根节点往目标元素(div#card
)移动,依次经过为:
window
document(文档根元素,在 HTML 中没有显式声明)
document.documentElement(<html> )
document.body(<body>)
...
目标元素 div#card
和调用事件对象的 event.composedPath() 方法拿到的 事件路径 类似。
window 看起来是个全局变量,但它也是可以绑定事件的,比如窗口大小改变的 resize 事件就只能绑定到 window 上,而不能绑定到 document 上。
然后再执行 冒泡阶段,然后反着再经过一遍这些节点。
我们会根据事件流经过的顺序,依次执行这些节点上绑定的对应事件函数。
3.应用场景
应用场景1:
<ul>
<li>小王<button data-user-id="5">聊天</button></li>
<li>小明<button data-user-id="99">聊天</button></li>
<!-- ... -->
<li>老王<button data-user-id="63">聊天</button></li>
</ul>
<script>
document.querySelector('ul').addEventListener('click', (event) => {
const target = event.target;
const userId = target.getAttribute('data-user-id');
if (userId) {
joinChat(userId);
}
});
</script>
代码解析:
将事件绑定到 ul 节点上,执行函数时,通过 event 对象拿到必要的信息,进行统一的操作。通过 event.target 我们能获得这次事件流的目标节点,然后从该节点对象中提取出需要的信息。这样,不管 li 有多少,更新多频繁,我们只需要维护一个函数就够了。
应用场景2:
<html>
<head>
<meta charset="UTF-8">
<title>demo</title>
</head>
<body>
<ul>
<li>item1</li>
<li>item2</li>
<li>item3</li>
<li>item4</li>
<li>item5</li>
</ul>
<script type="text/javascript">
// 找到所有的li
var liList = document.getElementsByTagName('li');
// 遍历所有的li,并给每个li添加点击事件
for (var i=0;i<liList.length;i++){
liList[i].onclick = function () {
alert(this.innerHTML);
}
}
</script>
</body>
</html>
代码解析:
给5个li标签加了点击事件,当界面上点击li时,会打印它们各自li标签显示的内容。
出现的问题:此时5个li,看起来每个li的点击事件触发时调用的都是同一个函数,即:
function () {
alert(this.innerHTML);
}
但其实并不是这样。每个li绑定的都是一个全新的函数,只不过每个函数的样子都一模一样。
如何验证这个结论呢?很简单,判断每个li标签的onclick是否相等就可以了。
代码验证如下:
alert(liList[0].onclick==liList[1].onclick);
//弹出false
至于上面说的函数长得一样,但不是同一个数据这种情况就类似于 var obj1 = {name:"jack",age:16}
和var obj2 = {name:"jack",age:16},obj1 == obj2
得到的结果也会是false
。
至此,我们可以得到结论,如果有5个li,那么就有5个函数会被创建在内存中占据空间,那如果有100个li呢?就会有100个长相一模一样的函数在内存中常驻,对内存的开销是巨大的!
解决办法:利用事件冒泡的原理,把事件加在父元素(ul)身上!
代码如下:
var ul = document.getElementsByTagName('ul')[0];
// 只用给ul加点击事件即可
ul.onclick = function (e) { //事件对象在ie8中要通过window.event才能取到,因此e = e || window.event是做兼容处理
e = e || window.event;
// e.target指的是被点击的那个li
alert(e.target.innerHTML);
}
原理解析:
回顾事件冒泡
事件冒泡:即一个元素的事件触发后,会依次一级一级往上调用父级元素的同名事件,直到window(注:IE8和之前的浏览器只到document)
例:div > p > span 当div和p以及span都添加了click事件,当点击span时,会依次向上触发span、p、div的click事件。其中,在每个触发的事件里,通过事件对象.target能拿到触发事件的源头元素,也就是 事件源alert(e.target),结果都是[object HTMLSpanElement].
在回顾完事件冒泡后,我们显而易见得到结论:给所有li添加点击事件,只要加到它们的父元素ul身上的根本原因是利用了事件冒泡。也即:无论点击哪个li,都会自动触发ul的点击事件,然后在ul里通过e.target能获得真正被点击的那个li,继而拿到它的innerHTML
小结:如果给一堆元素加事件,并且事件触发时执行的代码都差不多时,就可以把事件加在父元素身上啦!
看,这样的形式是不是就相当于把自己的事件,委托在父元素身上处理了呢?因而它才会叫事件委托!
思考:如果ul里还有其他子元素例如span,可我只想给li加点击事件,用原来写的事件委托还行吗?
答案是否定的,因为根据事件冒泡原理,所有子元素点击后都会触发父元素的点击,因此,如果你点击了span,也会调用ul的点击事件,这就相当于给span也加了点击事件。这时候该怎么解决呢?
很简单,只要判断一下事件源是不是li就行了,如果是li才执行代码,否则不执行,代码如下:
<html>
<head>
<meta charset="UTF-8">
<title>demo</title>
</head>
<body>
<ul>
<li>item1</li>
<li>item2</li>
<li>item3</li>
<li>item4</li>
<li>item5</li>
<span>我是span</span>
</ul>
<script type="text/javascript">
var ul = document.getElementsByTagName('ul')[0];
// 只用给ul加点击事件即可
ul.onclick = function (e) {
e = e || window.event;
// 判断事件源是不是li
if(e.target.nodeName.toLowerCase()=='li'){
// e.target指的是被点击的那个li
alert(e.target.innerHTML);
}
}
</script>
</body>
</html>
jQuery事件委托语法:
$('父元素').on('事件名','哪个子元素触发',传给回调函数的参数,事件触发时的回调函数);
解释:
1).参数1:事件名,代表要绑定什么事件,但是记得不用加on,也就是说如果你想加点击事件,只要写’click’即可,注意是字符串!所以要打单引号或者双引号
2).参数2:只能由哪个子元素触发,例如我写 “li”,就代表只能由这个父元素里面的li触发事件,其他子元素不会触发。需要注意的是,这也是字符串,并且,参数2可以不写,那就代表仅仅只是给父元素加一个点击事件,并且所有子元素都能触发到这个事件(因为事件冒泡)
3).参数3:其实一般不会用,就是如果想事件触发时,自己给回调函数传一些值就写,这个参数也可以不写!
4).参数4:事件触发时的回调函数
总结:参数1和参数4是必须的,其他是可选的,如果你要用事件委托,请写上参数2
$('ul').on('click','li','我是数据',function (e) {
console.log(e.data);
console.log(e.target.innerHTML);
// 或者
console.log(this.innerHTML);
})
说明:
1.on这个方法的参数3可以通过回调函数里的e.data拿到(但一般不会用,大家了解一下有这么个东西即可)
2.在jQuery事件委托的回调函数里this和e.target是同一个东西,但是在JS里this和e.target不是同一个东西
出处:www.l1mn.com
原文标题:js事件委托
原文地址:https://www.l1mn.com/p/wquaa1.html
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
Copyright © L1MN.COM 联系方式:l1mnfw@163.com