目录
e.target.nodeName 和 e.currentTarget.nodeName 区别
事件委托/事件代理
包含几个阶段?
三个阶段:捕获阶段 => 目标阶段 => 冒泡阶段
捕获阶段:
从上到下,从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标签时控制台输出信息如下:
addEventListener第三个参数
- 默认值为冒泡
- true:代表在捕获阶段执行
- false或者不填:代表在冒泡阶段执行
e.target.nodeName 和 e.currentTarget.nodeName 区别
- e.target.nodeName:指当前点击的元素
- e.currentTarget.nodeName: 绑定事件监听的元素
如何阻止事件的传播
e.stopPropagation();
注意:它不是阻止冒泡,而是阻止的事件的传播!!!事件的捕获和冒泡都会阻止掉!!!
在以上代码的监听 parent 点击事件中加入 e.stopPropagation(); 会发生什么?
parent.addEventListener('click', function(e) {
e.stopPropagation();
console.log('parent 捕获', e.target.nodeName, e.currentTarget.nodeName);
}, true);
点击span标签时控制台输出信息如下:
我们在监听 parent 点击事件中加入 e.stopPropagation(); 阻止了事件传播,走到 parent 事件时,当前事件已经触发了所以会打印出 「parent 捕获 SPAN DIV」,但是会阻止后续事件的传播。
如果在冒泡事件里面阻止事件的传播呢?
child.addEventListener('click', function(e) {
e.stopPropagation();
console.log('child 冒泡', e.target.nodeName, e.currentTarget.nodeName);
}, false);
捕获阶段没做拦截,整个捕获阶段会完整的走完,但是冒泡阶段到了child就会走完,因为我们在child里面拦截了它。
现在有一个场景面试题
问题:现在有一个页面,这个页面上有许多的元素,div p button 等,每个元素上都有自己的 click 事件,都不相同。
需求:一个用户进入这个页面的时候,会有一个状态 banned,window.banned 。
true:表示当前用户被封禁了,用户点击当点页面上的任何元素,都不执行原有的click逻辑,而是 alert 弹窗,提示你被封禁了!!!
false:不做任何操作
答:在最上层捕获事件中做拦截,比如window,如果banned为true,就直接在捕获阶段阻止事件传播,并且弹窗提示,否则不进行任何操作。
window.addEventListener('click', function(e) {
if(banned) {
e.stopPropagation();
alert('你被封禁了!!!');
return
}
console.log('window 捕获', e.target.nodeName, e.currentTarget.nodeName);
}, true);
效果:当banned为true时,控制台不会打印任何信息,会弹窗提示。未false 则不做任何操作;
阻止事件默认行为
e.preventDefault();
什么叫做默认行为
比如点击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标签默认事件
})
兼容性
属性 | 说明 |
---|---|
addEventListener | 只支持 Firefox、Chrome、IE高版本、Safari、Opera |
attachEvent | 兼容IE7、IE8 |
封装一个监听的方法
我们很难真正的去一个元素上加一个方法之类的,那样要去操作原型链,先简单通过一个类的方式去封装。
class BomEvent {
constructor(element) {
this.element = element;
}
/**
* @param { 事件类型 } type
* @param { 事件触发后的回调 } handler
*/
addEvent(type, handler) {
/**
* 通过if判断
* 分别判断 addEventListener、attachEvent、以及不存在它们的情况下去怎么做
*/
if(this.element.addEventListener) {
/* 走冒泡形式,因为IE不支持事件的捕获 */
this.element.addEventListener(type, handler, false);
} else if(this.element.attachEvent) {
this.element.attachEvent(`on${type}`, handler)
/**
* 如果在attachEvent情况下考虑 IE7、IE8不支持箭头函数的情况下
* 把handler换成
* function() { handler.call(element) };
*/
} else {
/**
* 可能有一些更奇怪的浏览器,attachEvent 都没有
* 我们直接给element上面绑定元素
* 直接在 handler属性上去绑
*/
this.element[`on${type}`] = handler;
}
}
/**
* 和 addEvent几乎是一样的
* 只不过调用的API不同
*/
removeEvent(type, handler) {
if(this.element.removeEventListener) {
this.element.removeEventListener(type, handler, false);
} else if(this.element.detachEvent) {
this.element.detachEvent(`on${type}`, handler)
} else {
this.element[`on${type}`] = null;
}
}
}
封装一个阻止事件的传播的方法
function stopPropagation(event) {
if(event.stopPropagation) {
event.stopPropagation(); // 标准 w3c浏览器
} else {
event.cancelBubble = true; // IE
}
}
封装一个阻止事件默认行为的方法
function preventDefault(event) {
if(event.preventDefault) {
event.preventDefault(); // 标准 w3c浏览器
} else {
event.returnValue = false; // IE
}
}