JavaScript之“冒泡”和“捕捉”详解(全网精品博文)
到底什么是“冒泡”?
让我们首先通过一个简单的例子来感性的认识一下,JavaScript的冒泡到底指的是什么东西。
先看下面一个简短的代码片段:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>文档标题</title>
</head>
<body>
<div onclick="alert('div元素的onClick事件发生了响应')">
<em>如果你点击<code>EM</code>, 在 <code>DIV元素</code>上绑定的onClick响应函数将会执行</em>
</div>
</body>
</html>
我们在<div>元素上绑定了一个onClick事件发生时的响应函数,让其发出一段警告信息。
这个响应函数虽然是绑定在<div>元素上的,但是当你点击<div>的子元素<em>或者<code>时,<div>元素上的onClick事件一样会被触发,并且执行相应的响应函数。
同学们可以在本地用记事本或者IDE亲自实验一下,或者点击此处进入菜鸟工具提供的HTML/CSS/JS在线开发工具。
使用在线的IDE开发工具,我们可以很方便的在浏览器端直接进行测试。
我们只需要把代码复制后黏贴到在线工具的HTML部分就可以执行了,如下图所示:
当我们点击“em”或者“code元素”时,浏览器将弹出如下所示的警告:
那么问题来了,我们明明只在<div>元素上绑定了onClick事件的响应函数,为什么点击<em>元素和<code>元素,<div>元素上绑定的onClick事件一样会被触发呢?
“冒泡”的原理
“冒泡”的原理其实非常的简单。
当一个“元素”上面的某个“事件”发生时,这个“元素”上面相对应的“响应函数”将会被调用。然后,这个元素的父元素如果也存在这个“事件”相对应的“响应函数”,父元素上的“响应函数”也会被调用,就这样一直传递到其他的“祖先”元素。类似于古代的“株连九族”,某个人犯错了,他的祖先都要遭到连累,这个事件就像冒泡泡一样,从最内层的元素一直向上层的元素传播,直到传递到不能传递为止。
让我们来看看下面这个例子:
首先进入“”菜鸟工具“”提供的在线HTML编辑工具,分别在“HTML栏”和“CSS栏”输入下面的代码:
HTML栏:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>文档标题</title>
</head>
<body>
<form onclick="alert('form')">FORM
<div onclick="alert('div')">DIV
<p onclick="alert('p')">P</p>
</div>
</form>
</body>
</html>
CSS栏:
body * {
margin: 10px;
border: 1px solid blue;
}
我们将会看到像下面这样被渲染的网页:
我们首先点击最里面的<p>标签,看看会发生什么?
首先<p>标签上面的“响应函数”被执行了,浏览器弹出“警告框p”,接下来<div>元素上面的“响应函数”也被执行了,又弹出了一次“警告框”,接着<form>元素上面的“响应函数”也被执行了......
再点击<div>元素看看发生了什么?
直接点击<form>元素呢?
做完这个实验,相信你已经发现了些什么,如果没有发现也不用急。
实际上,冒泡就像下面这张图说明的一样:
最“深”层的对象产生的事件不仅会被自身的响应函数所捕获,也会被该对象的所有“祖先”元素所捕获,如果恰好所有“祖先”元素都定义了与该事件相关联的“响应函数”,则这些响应函数都会被执行。正如我们上面看到的,不仅<p>元素上面定义了click事件的“监听器”和“响应函数”,他的所有“祖先”元素:<div>和<form>一样也定义了click事件的监听器”和“响应函数”,因此也会被执行,所以我们点击一次<p>元素,却产生了3次响应。正所谓:“好事不出门,坏事传千里”。
因此,当我们点击<p>元素时,我们将会看到3个警告框:p——>div——form。
向上面这样,子元素产生的事件像冒泡泡一样,会传递到其所有的“祖先”元素的过程,我们就叫做“冒泡”。
event.target对象
父元素上的“事件监听器”总是能够获得“事件”发生的“实际详细细节”,比如这个事件是由哪个元素产生的。
产生“事件”的元素叫做“目标元素”,即"target element",我们可以通过event.target获得该元素。
举个栗子来说:如果我们在<form>元素上有一个click事件处理器,这个处理器能够捕捉<form>元素内所有元素发出的click事件,也就是说,不论哪个元素产生了click事件,这个事件都会因为“冒泡”机制而传递到<form>元素上的。<form>元素从而会执行相应的响应函数。
让我们来做下面这个实验:(进入菜鸟HTML在线工具)
HTML栏:
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="example.css">
</head>
<body>
A click shows both <code>event.target</code> and <code>this</code> to compare:
<form id="form">FORM
<div>DIV
<p>P</p>
</div>
</form>
<script src="script.js"></script>
</body>
</html>
CSS栏:
form {
background-color: green;
position: relative;
width: 150px;
height: 150px;
text-align: center;
cursor: pointer;
}
div {
background-color: blue;
position: absolute;
top: 25px;
left: 25px;
width: 100px;
height: 100px;
}
p {
background-color: red;
position: absolute;
top: 25px;
left: 25px;
width: 50px;
height: 50px;
line-height: 50px;
margin: 0;
}
body {
line-height: 25px;
font-size: 16px;
}
JavaScript栏:
form.onclick = function(event) {
event.target.style.backgroundColor = 'yellow';
// chrome needs some time to paint yellow
setTimeout(() => {
alert("target = " + event.target.tagName + ", this=" + this.tagName);
event.target.style.backgroundColor = ''
}, 0);
};
运行后,我们将会看到下面的界面:
我们试着分别点击一下<p>元素,<div>元素和<form>元素,看看event.target和event.currentTarget元素在不同情况下分别指向什么。
我们会发现,当点击<form>元素的时候,此时event.target是等于this的。
停止“冒泡”
一个“冒泡事件”会从产生该事件的元素一直向它的父元素传递,直到传到<html>元素,然后到document对象,一些事件甚至可以“冒泡”传递到windows对象上,在事件传递的路径中,其经过的所有元素上相对应的处理器(响应函数)都会被触发。
但是任何处理器(即监听事件和响应函数)都有能力决定某个事件是任由其继续向上层元素“冒泡传递”还是直接停止。
停止事件继续“冒泡传递”的方法为:event.stopPropagation( )。
让我们来举一个例子(将下面的HTML代码直接粘贴到菜鸟工具),在下面这个例子中:如果你点击<button>元素,<body>上的onClick( )事件并不会被触发。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>文档标题</title>
</head>
<body onclick="alert(`冒泡事件到达了body元素`)">
<button onclick="event.stopPropagation()">点击我</button>
</body>
</html>
注意:如果不是确实需要,请尽量不要停止“冒泡”!
“冒泡”机制对于开发者来说是很方便的,如果不是确实需要关闭“冒泡”机制,请不要关闭它。
有的时候,event.stopPropagation()会产生隐藏的缺陷以至于最后变成一个很大的问题。
“捕捉”
前面我们讲了“冒泡”机制是如何工作的,其实在javascript中还存在另外一种事件处理机制叫做“捕捉”(Capturing)。
标准的DOM 事件描述了3种事件传播机制:
1.捕捉机制:事件从最外部的元素向内部传播。
2.目标机制:事件到达目标元素。
3.冒泡机制:事件从内部向外部传播。
下面这张图描述了当点击<td>元素时,事件在不同机制下的传播路径:
可能看图还不太够理解,我们可以亲自来实验一下“冒泡”机制和“捕捉”机制的不同:
我们仍然使用菜鸟工具的HTML在线工具来验证,在相应的区域输入下面的代码:
HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>文档标题</title>
</head>
<body>
<form>FORM
<div>DIV
<p>P</p>
</div>
</form>
</body>
</html>
CSS:
body * {
margin: 10px;
border: 1px solid blue;
}
javaScript:
for(let elem of document.querySelectorAll('*')) {
elem.addEventListener("click", e => alert(`现在是捕捉机制的事件传递过程: ${elem.tagName}`), true);
//设置为true表明使用“捕捉机制”,否则默认使用“冒泡机制”
elem.addEventListener("click", e => alert(`现在是冒泡机制的事件传递过程: ${elem.tagName}`));
}
完成上面的输入后,我们将会看到下面的界面:
上面的JS代码给每一个元素都加上了Click事件的响应方法,这样就能很好的看到究竟是哪一个元素上的Click事件被触发了,从而能够直观地看到“捕捉”机制和“冒泡”机制的整个过程。
当我们点击最内部的<p>元素时,此时的触发序列应该是:
1.HTML——>BODY——>FORM——>DIV——>P(捕捉机制生效),接下来是:
2.P——>DIV——>FORM——>BODY——>HTML(冒泡机制生效)
如果细心的同学就会发现,<p>元素出现了2次,在“捕捉”机制结束的时候和“冒泡”机制开始的时候。
经过本次实验的你,是不是对“冒泡”机制和“捕捉”机制又有了更深的了解呢?
其实在实际的开发当中,“捕捉”机制非常少用,通常我们使用“冒泡”机制来传递事件。
参考文献
【1】https://javascript.info/bubbling-and-capturing 翻译:刘扬俊
博客文章版权声明