目录
1. 基本概念
什么叫事件委托呢?它还有一个名字叫事件代理,JavaScript高级程序设计上讲:事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
1.1 原理
事件代理(Event Delegation),又称之为事件委托。是JavaScript中常用绑定事件的常用技巧。顾名思义,“事件代理”即是把原本需要绑定在子元素的响应事件(click、keydown......)委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。
举个通俗的例子
比如一个宿舍的同学同时快递到了,一种方法就是他们一个个去领取,还有一种方法就是把这件事情委托给宿舍长,让一个人出去拿好所有快递,然后再根据收件人一 一分发给每个宿舍同学;
在这里,取快递就是一个事件,每个同学指的是需要响应事件的 DOM 元素,而出去统一领取快递的宿舍长就是代理的元素,所以真正绑定事件的是这个元素,按照收件人分发快递的过程就是在事件执行中,需要判断当前响应的事件应该匹配到被代理元素中的哪一个或者哪几个。
2. 事件冒泡和事件捕获
前面提到事件委托的原理是DOM元素的事件冒泡,那么事件冒泡是什么呢?
事件冒泡就是事件从最深的节点开始,然后逐步向上传播事件。举个例子:页面上有这么一个节点树,div>ul>li>a;比如给最里面的a加一个click点击事件,那么这个事件就会一层一层的往外执行,执行顺序a>li>ul>div,有这样一个机制,那么我们给最外面的div加点击事件,那么里面的ul,li,a做点击事件的时候,都会冒泡到最外层的div上,所以都会触发,这就是事件委托,委托它们父级代为执行事件。
一个事件触发后,会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段
三个阶段:捕获阶段 => 目标阶段 => 冒泡阶段
如上图所示,事件传播分成三个阶段:
- 捕获阶段:从window对象传导到目标节点(上层传到底层)称为“捕获阶段”(capture phase),捕获阶段不会响应任何事件;
- 目标阶段:在目标节点上触发,称为“目标阶段”
- 冒泡阶段:从目标节点传导回window对象(从底层传回上层),称为“冒泡阶段”(bubbling phase)。事件代理即是利用事件冒泡的机制把里层所需要响应的事件绑定到外层;
捕获阶段:
从上到下,从window到你点击的目标节点,不如点击一个 input
window => body => inpiut => body => window
代码演示
-
<body>
-
<div id="parent" class="flex-center">
-
parent
-
<p id="child" class="flex-center">
-
child
-
<span id="son" class="flex-center">
-
<a id="aTag" href="https://baidu.com">点我啊
</a>
-
</span>
-
</p>
-
</div>
-
</body>
-
<script type="text/javascript">
-
const parent =
document.
getElementById(
'parent');
-
const child =
document.
getElementById(
'child');
-
const son =
document.
getElementById(
'son');
-
const aTag =
document.
getElementById(
'aTag');
-
-
aTag.
addEventListener(
'click',
function(
e) {
-
e.
preventDefault();
// 阻止A标签默认事件
-
})
-
-
window.
addEventListener(
'click',
function(
e) {
-
// e.target.nodeName 和 e.currentTarget.nodeName 下面会讲这两个参数的意思
-
console.
log(
'window 捕获', e.
target.
nodeName, e.
currentTarget.
nodeName);
-
},
true);
-
// addEventListener第三个参数 true代表在捕获阶段执行。false或者不填代表在冒泡阶段执行。
-
-
parent.
addEventListener(
'click',
function(
e) {
-
console.
log(
'parent 捕获', e.
target.
nodeName, e.
currentTarget.
nodeName);
-
},
true);
-
-
child.
addEventListener(
'click',
function(
e) {
-
console.
log(
'child 捕获', e.
target.
nodeName, e.
currentTarget.
nodeName);
-
},
true);
-
-
son.
addEventListener(
'click',
function(
e) {
-
console.
log(
'son 捕获', e.
target.
nodeName, e.
currentTarget.
nodeName);
-
},
true);
-
-
son.
addEventListener(
'click',
function(
e) {
-
console.
log(
'son 冒泡', e.
target.
nodeName, e.
currentTarget.
nodeName);
-
},
false);
-
-
child.
addEventListener(
'click',
function(
e) {
-
console.
log(
'child 冒泡', e.
target.
nodeName, e.
currentTarget.
nodeName);
-
},
false);
-
-
parent.
addEventListener(
'click',
function(
e) {
-
console.
log(
'parent 冒泡', e.
target.
nodeName, e.
currentTarget.
nodeName);
-
},
false);
-
-
window.
addEventListener(
'click',
function(
e) {
-
console.
log(
'window 冒泡', e.
target.
nodeName, e.
currentTarget.
nodeName);
-
},
false);
-
</script>
点击span标签时控制台输出信息如下:
3. addEventListener的第三个参数
addEventListener方法用来为一个特定的元素绑定一个事件处理函数,是JavaScript中的常用方法。addEventListener有三个参数:
element.addEventListener(event, function, useCapture)
addEventListener第三个参数
- 默认值为冒泡
- true:代表在捕获阶段执行
- false或者不填:代表在冒泡阶段执行
4. 事件委托阶段案例
4.1 事件冒泡案例
(第三个参数是false的时候是冒泡)
-
var body=
document.
getElementsByTagName(
'body')[
0];
-
-
window.
addEventListener(
'click',
function(
){
-
console.
log(
'window')
-
},
false)
-
body.
addEventListener(
'click',
function(
){
-
console.
log(
'body')
-
},
false)
-
-
var oDiv=
document.
getElementsByTagName(
'div')[
0];
-
oDiv.
addEventListener(
'click',
function(
){
-
console.
log(
1)
-
},
false)
-
-
oDiv.
addEventListener(
'click',
function(
){
-
console.
log(
2)
-
},
false)点击div运行结果
点击div运行结果
4.2 事件捕获案例
-
var body=
document.
getElementsByTagName(
'body')[
0];
-
-
window.
addEventListener(
'click',
function(
){
-
console.
log(
'window')
-
},
true)
-
body.
addEventListener(
'click',
function(
){
-
console.
log(
'body')
-
},
true)
-
-
var oDiv=
document.
getElementsByTagName(
'div')[
0];
-
oDiv.
addEventListener(
'click',
function(
){
-
console.
log(
1)
-
},
true)
-
-
oDiv.
addEventListener(
'click',
function(
){
-
console.
log(
2)
-
},
true)
点击div运行结果
事件捕获就是从上往下一级一级往下找,先找父级在找子级
5. 经典面试题
6. 事件代理总结
使用事件代理的好处不仅在于将多个事件处理函数减为一个,而且对于不同的元素可以有不同的处理方法。假如上述列表元素当中添加了其他的元素节点(如:a、span等),我们不必再一次循环给每一个元素绑定事件,直接修改事件代理的事件处理函数即可。
6.1 冒泡还是捕获?
对于事件代理来说,在事件捕获或者事件冒泡阶段处理并没有明显的优劣之分,但是由于事件冒泡的事件流模型被所有主流的浏览器兼容,从兼容性角度来说还是建议大家使用事件冒泡模型。
6.2 阻止事件冒泡
1. 给子级加 event.stopPropagation( )
-
$(
"#div1").
mousedown(
function(
e){
-
var e=event||
window.
event;
-
event.
stopPropagation();
-
});
2. 在事件处理函数中返回 false
-
$(
"#div1").
mousedown(
function(
event){
-
var e=e||
window.
event;
-
return
false;
-
});
但是这两种方式是有区别的。return false
不仅阻止了事件往上冒泡,而且阻止了事件本身(默认事件)。event.stopPropagation()
则只阻止事件往上冒泡,不阻止事件本身。
3. event.target==event.currentTarget,让触发事件的元素等于绑定事件的元素,也可以阻止事件冒泡;
6.4 阻止默认事件
(1)event.preventDefault( )
(2)return false
什么叫做默认行为
比如点击a标签会跳转到另个页面,比如拖拽到一张图片到浏览器,浏览器会打开这个图片,比如点击表单提交按钮,会提交当前表单……
如果我们不希望这些默认行为的发生,我们应该怎么做?
最开始写的代码中有个a标签,点击时是要跳转到百度的
<a id="aTag" href="https://baidu.com">点我啊</a>
如果我们不想让他跳转到百度,在a标签事件上做个拦截,当点击 a标签时,就不会跳转到百度。
-
const aTag =
document.
getElementById(
'aTag');
-
-
aTag.
addEventListener(
'click',
function(
e) {
-
e.
preventDefault();
// 阻止a标签默认事件
-
})
转载:https://blog.csdn.net/pakerder/article/details/127538711