假如对事宜也许相识,可以晓得有事宜冒泡这回事,然则冒泡、捕捉、流传这些机制可以还没有深切的研讨实践一下,我抽时间整理了一下相干的学问。
- 本文重要对事宜机制一些细节举行议论,过于基本的事宜绑定学问要领没有引见。
- 迥殊少的篇幅关注阅读器兼容问题,毕竟道理相识了,兼容性问题可以本身想办法处理了。
在阅读器相对规范化之前,各个阅读器厂商都是本身完成的事宜模子,有的用了冒泡,有的用了捕捉,W3C为了统筹之前的规范,将事宜发作定义成以下三个阶段:
1、捕捉阶段
2、目的阶段
3、冒泡阶段
只是硬生生的说事宜机制究竟是怎样回事不轻易明白,用一个demo为主线申明事宜的道理比较轻易明白:
HTML
<body>
<div id="wrapDiv">wrapDiv
<p id="innerP">innerP
<span id="textSpan">textSpan</span>
</p>
</div>
</body>
CSS
<style>
#wrapDiv, #innerP, #textSpan{
margin: 5px;
padding: 5px;
box-sizing: border-box;
cursor: default;
}
#wrapDiv{
width: 300px;
height: 300px;
border: indianred 3px solid;
}
#innerP{
width: 200px;
height: 200px;
border: hotpink 3px solid;
}
#textSpan{
display: block;
width: 100px;
height: 100px;
border: orange 3px solid;
}
</style>
JavaScript
<script>
var wrapDiv = document.getElementById("wrapDiv");
var innerP = document.getElementById("innerP");
var textSpan = document.getElementById("textSpan");
// 捕捉阶段绑定事宜
window.addEventListener("click", function(e){
console.log("window 捕捉", e.target.nodeName, e.currentTarget.nodeName);
}, true);
document.addEventListener("click", function(e){
console.log("document 捕捉", e.target.nodeName, e.currentTarget.nodeName);
}, true);
document.documentElement.addEventListener("click", function(e){
console.log("documentElement 捕捉", e.target.nodeName, e.currentTarget.nodeName);
}, true);
document.body.addEventListener("click", function(e){
console.log("body 捕捉", e.target.nodeName, e.currentTarget.nodeName);
}, true);
wrapDiv.addEventListener("click", function(e){
console.log("wrapDiv 捕捉", e.target.nodeName, e.currentTarget.nodeName);
}, true);
innerP.addEventListener("click", function(e){
console.log("innerP 捕捉", e.target.nodeName, e.currentTarget.nodeName);
}, true);
textSpan.addEventListener("click", function(e){
console.log("textSpan 捕捉", e.target.nodeName, e.currentTarget.nodeName);
}, true);
// 冒泡阶段绑定的事宜
window.addEventListener("click", function(e){
console.log("window 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
document.addEventListener("click", function(e){
console.log("document 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
document.documentElement.addEventListener("click", function(e){
console.log("documentElement 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
document.body.addEventListener("click", function(e){
console.log("body 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
wrapDiv.addEventListener("click", function(e){
console.log("wrapDiv 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
innerP.addEventListener("click", function(e){
console.log("innerP 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
textSpan.addEventListener("click", function(e){
console.log("textSpan 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
</script>
demo页面效果图
这个时刻,假如点击一下textSpan
这个元素,控制台会打印出如许的内容:
当按下鼠标点击后,究竟发作了什么的,如今我基于上面的例子来讲一下:
capture=>start: 捕捉阶段最先
window=>operation: window
document=>operation: document
documentElement=>operation: documentElement
body=>operation: body
wrapDiv=>operation: wrapDiv
innerP=>operation: innerP
target=>start: 捕捉阶段完毕,目的阶段最先
textSpan=>operation: textSpan
textSpan2=>operation: textSpan
bubble=>start: 目的阶段完毕,冒泡阶段最先
innerP2=>operation: innerP
wrapDiv2=>operation: wrapDiv
body2=>operation: body
documentElement2=>operation: documentElement
document2=>operation: document
window2=>operation: window
bubbleend=>start: 冒泡阶段完毕
capture->window->document->documentElement->body->wrapDiv->innerP->target->textSpan->textSpan2->bubble->innerP2->wrapDiv2->body2->documentElement2->document2->window2->bubbleend
从上面所画的事宜流传的历程可以看出来,当点击鼠标后,会先发作事宜的捕捉
- 捕捉阶段:起首
window
会获捕捉到事宜,以后document
、documentElement
、body
会捕捉到,再以后就是在body中DOM元素一层一层的捕捉到事宜,有wrapDiv
、innerP
。- 目的阶段:真正点击的元素
textSpan
的事宜发作了两次,因为在上面的JavaScript代码中,textSapn
既在捕捉阶段
绑定了事宜,又在冒泡阶段
绑定了事宜,所以发作了两次。然则这里有一点是须要注重,在目的阶段并不一定先发作在捕捉阶段所绑定的事宜,而是先绑定的事宜发作,一会会诠释一下。- 冒泡阶段:会和捕捉阶段相反的步骤将事宜一步一步的冒泡到
window
那可以有一个疑问,我们不必addEventListener
绑定的事宜会发作在哪一个阶段呢,我们来一个测试,趁便再演示一下我在上面的目的阶段所说的目的阶段并不一定先发作捕捉阶段所绑定的事宜
是怎样一回事。
我们从新改一下JavaScript
代码:
<script>
var wrapDiv = document.getElementById("wrapDiv");
var innerP = document.getElementById("innerP");
var textSpan = document.getElementById("textSpan");
// 测试直接绑定的事宜究竟发作在哪一个阶段
wrapDiv.onclick = function(){
console.log("wrapDiv onclick 测试直接绑定的事宜究竟发作在哪一个阶段")
};
// 捕捉阶段绑定事宜
window.addEventListener("click", function(e){
console.log("window 捕捉", e.target.nodeName, e.currentTarget.nodeName);
}, true);
document.addEventListener("click", function(e){
console.log("document 捕捉", e.target.nodeName, e.currentTarget.nodeName);
}, true);
document.documentElement.addEventListener("click", function(e){
console.log("documentElement 捕捉", e.target.nodeName, e.currentTarget.nodeName);
}, true);
document.body.addEventListener("click", function(e){
console.log("body 捕捉", e.target.nodeName, e.currentTarget.nodeName);
}, true);
wrapDiv.addEventListener("click", function(e){
console.log("wrapDiv 捕捉", e.target.nodeName, e.currentTarget.nodeName);
}, true);
innerP.addEventListener("click", function(e){
console.log("innerP 捕捉", e.target.nodeName, e.currentTarget.nodeName);
}, true);
textSpan.addEventListener("click", function(){
console.log("textSpan 冒泡 在捕捉之前绑定的")
}, false);
textSpan.onclick = function(){
console.log("textSpan onclick")
};
textSpan.addEventListener("click", function(e){
console.log("textSpan 捕捉", e.target.nodeName, e.currentTarget.nodeName);
}, true);
// 冒泡阶段绑定的事宜
window.addEventListener("click", function(e){
console.log("window 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
document.addEventListener("click", function(e){
console.log("document 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
document.documentElement.addEventListener("click", function(e){
console.log("documentElement 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
document.body.addEventListener("click", function(e){
console.log("body 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
wrapDiv.addEventListener("click", function(e){
console.log("wrapDiv 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
innerP.addEventListener("click", function(e){
console.log("innerP 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
textSpan.addEventListener("click", function(e){
console.log("textSpan 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
</script>
再看控制台的效果:
- 图中第一个被圈出来的诠释:
textSpan
是被点击的元素,也就是目的元素,一切在textSpan
上绑定的事宜都邑发作在目的阶段
,在绑定捕捉代码之前写了绑定的冒泡阶段的代码,所以在目的元素
上就不会恪守先发作捕捉后发作冒泡这一划定规矩,而是先绑定的事宜先发作。- 图中第二个被圈出来的诠释:因为
wrapDiv
不是目的元素,所以它上面绑定的事宜会恪守先发作捕捉后发作冒泡的划定规矩。所以很明显用onclick
直接绑定的事宜发作在了冒泡阶段。
target和currentTarget
上面的代码中写了e.target
和e.currentTarget
,还没有说是什么,target
和currentTarget
都是event
上面的属性,target
是真正发作事宜的DOM元素,而currentTarget
是当前事宜发作在哪一个DOM元素上。
可以连系控制台打印出来的信息明白下,目的阶段
也就是 target == currentTarget
的时刻。我没有打印它们两个因为太长了,所以打印了它们的nodeName
,然则因为window
没有nodeName
这个属性,所以是undefined
。
阻挠事宜流传
说到事宜,一定要说的是怎样阻挠事宜流传。总是有许多帖子说e.stopPropagation()
是阻挠事宜的冒泡的流传,实际上这么说并非很正确,因为它不仅可以阻挠事宜在冒泡阶段的流传,还能阻挠事宜在捕捉阶段的流传。
来看一下我们再改一下的JavaScript代码:
<script>
var wrapDiv = document.getElementById("wrapDiv");
var innerP = document.getElementById("innerP");
var textSpan = document.getElementById("textSpan");
// 测试直接绑定的事宜究竟发作在哪一个阶段
wrapDiv.onclick = function(){
console.log("wrapDiv onclick 测试直接绑定的事宜究竟发作在哪一个阶段")
};
// 捕捉阶段绑定事宜
window.addEventListener("click", function(e){
console.log("window 捕捉", e.target.nodeName, e.currentTarget.nodeName);
}, true);
document.addEventListener("click", function(e){
console.log("document 捕捉", e.target.nodeName, e.currentTarget.nodeName);
}, true);
document.documentElement.addEventListener("click", function(e){
console.log("documentElement 捕捉", e.target.nodeName, e.currentTarget.nodeName);
}, true);
document.body.addEventListener("click", function(e){
console.log("body 捕捉", e.target.nodeName, e.currentTarget.nodeName);
}, true);
wrapDiv.addEventListener("click", function(e){
console.log("wrapDiv 捕捉", e.target.nodeName, e.currentTarget.nodeName);
// 在捕捉阶段阻挠事宜的流传
e.stopPropagation();
}, true);
innerP.addEventListener("click", function(e){
console.log("innerP 捕捉", e.target.nodeName, e.currentTarget.nodeName);
}, true);
textSpan.addEventListener("click", function(){
console.log("textSpan 冒泡 在捕捉之前绑定的")
}, false);
textSpan.onclick = function(){
console.log("textSpan onclick")
};
textSpan.addEventListener("click", function(e){
console.log("textSpan 捕捉", e.target.nodeName, e.currentTarget.nodeName);
}, true);
// 冒泡阶段绑定的事宜
window.addEventListener("click", function(e){
console.log("window 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
document.addEventListener("click", function(e){
console.log("document 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
document.documentElement.addEventListener("click", function(e){
console.log("documentElement 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
document.body.addEventListener("click", function(e){
console.log("body 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
wrapDiv.addEventListener("click", function(e){
console.log("wrapDiv 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
innerP.addEventListener("click", function(e){
console.log("innerP 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
textSpan.addEventListener("click", function(e){
console.log("textSpan 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
</script>
我们在事宜的捕捉阶段阻挠了流传,看一下控制台的效果:
实际上我们点击的是textSpan
,然则因为在捕捉阶段事宜就被阻挠了流传,所以在textSpan
上绑定的事宜根本就没有发作,冒泡阶段绑定的事宜天然也不会发作,因为阻挠事宜在捕捉阶段流传的特征,e.stopPropagation()
很少用到在捕捉阶段去阻挠事宜的流传,人人就认为e.stopPropagation()
只能阻挠事宜在冒泡阶段流传。
阻挠事宜的默许行动
e.preventDefault()
可以阻挠事宜的默许行动发作,默许行动是指:点击a标签就转跳到其他页面、拖拽一个图片到阅读器会自动翻开、点击表单的提交按钮会提交表单等等,因为有的时刻我们并不愿望发作这些事变,所以须要阻挠默许行动,这块的学问比较简朴,可以本身去试一下。
与事宜相干的兼容性问题
这里只是简朴提一下兼容性问题,不做过量的睁开。关于绑定事宜,ie低版本的阅读器是用attachEvent
,而高版本ie和规范阅读器用的是addEventListener
,attachEvent
不能指定绑定事宜发作在捕捉阶段照样冒泡阶段,它只能将事宜绑定到冒泡阶段,然则并不意味这低版本的ie没有事宜捕捉,它也是先发作事宜捕捉,再发作事宜冒泡,只不过这个历程没法通历程序控制。
实在事宜的兼容性问题迥殊的多,比方猎取事宜对象的体式格局、绑定和消除绑定事宜的体式格局、目的元素的猎取体式格局等等,因为陈旧的阅读器究竟会被镌汰,不过量睁开了。
迎接关注【本期节目】,微信民众号ID:benqijiemu。
这里有:互联网思索、软件&东西引荐、前端手艺等。
可以在民众号复兴我,愿望和人人一同交换一切与互联网相干的事变~