这是早年前端学习路上写的一篇记录,工作了几年,现在开坑重新回顾一番
一、前置知识——事件流
当浏览器发展到第四代时(IE4和Netscape Communicator4),IE和Netscape开发团队遇到一个很有意思的问题:
页面哪个部分会拥有某个特定的事件? 举个例子:一张纸上有一组同心圆,当手指放在圆心上时,那么指向的不是一个圆,而是所有圆。两家公司的浏览器开发团队看待这个问题是一致的:点击按钮的同时,也单击了按钮的容器元素,甚至也单击了整个页面。
事件流描述的是从页面中接收事件的顺序。IE和Netscape两个团队提出了几乎完全相反的事件流的概念。IE的事件流是事件冒泡流,而Netscape的事件流是事件捕获流。
二、事件冒泡
事件冒泡,即从触发事件的节点,自下而上的去触发事件(从最具体的节点,逐级向上传播)
<!DOCTYPE html>
<html>
<head>
<title>deml</title>
</head>
<body>
<div id="myDiv">Click Me</div>
</body>
</html>
如果点击页面的div元素,则click的传播顺序是 div--->body--->html--->document 也就是说,click事件首先在div元素上发生,然后click事件沿DOM树向上传播,在每一级节点都会发生,直到传播到document对象。 例如body中有如下元素:
<div id="parent">
父元素
<div id="child">
子元素
</div>
</div>
对应的事件为:
<script type="text/javascript">
document.body.addEventListener("click", function(e) {
console.log("click-body--事件冒泡");
}, false);
document.getElementById("parent").addEventListener("click", function(e) {
console.log("click-parent--事件冒泡");
}, false);
document.getElementById("child").addEventListener("click", function(e) {
console.log("click-child--事件冒泡");
}, false);
</script>
当点击父元素、子元素对应的输出顺序为:
点击父元素
点击子元素
由此可了解到冒泡click事件是沿DOM树向上执行的
三、事件捕获
事件捕获从document到触发事件的那个节点,即自上而下的去触发事件,其思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。 以前面的HTML页面进行说明,那么单击div元素就会以下列顺序触发click事件
document--->html--->body--->div
在事件捕获过程中,document对象最先触发click事件,然后沿DOM树依次向下,一直传播到事件的实际目标。
添加父子元素对应事件
<script type="text/javascript">
document.body.addEventListener("click", function(e) {
console.log("click-body--事件捕获");
}, true);
document.getElementById("parent").addEventListener("click", function(e) {
console.log("click-parent--事件捕获");
}, true);
document.getElementById("child").addEventListener("click", function(e) {
console.log("click-child--事件捕获");
}, true);
</script>
点击父元素
点击子元素
验证了事件捕获是由上至下执行的。
四、DOM事件流
“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,为截获事件提供了机会。
在DOM事件流中,实际的目标在捕获阶段不会接收到事件。这意味着在捕获阶段,事件从document到body后就停止了。下一个阶段是“处于目标”阶段,于是事件在div上发生,并在事件处理中被看成冒泡阶段的一部分。
在事件过程中发现一个有趣的特点,当同时为目标元素添加事件冒泡与事件捕获,输出的顺序完全决定于script标签中两段代码的顺序。
五、事件委托
对“事件处理程序过多”问题的解决方案就是事件委托。事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理一类型的所有事件。实际操作中,就是给他们的一个共同父级元素添加一个事件函数去处理他们所有的事件情况。
<ul id="list">
<li>111</li>
<li>222</li>
<li>333</li>
<li>444</li>
</ul>
<script type="text/javascript">
document.getElementById('list').addEventListener('click',function(e){
e.target.innerHTML = "被点击";
});
</script>
按照传统的做法,若要为每个li标签添加onclick事件,需要逐个添加addEventListener。如果在一个复杂的Web应用程序中,对所有的可单击元素都采用这种方式,内存和性能上的压力会非常大。 在上面这段代码中,使用事件委托只为ul元素添加一个onclick事件处理程序。由于所有列表项都是元素的子节点,而且它们的事件会冒泡,所以单击事件最终会被这个函数处理。
六、addEventListener
// 默认为false,冒泡阶段触发
addEventListener('click', ()=>{}, false);
// 捕获阶段触发
addEventListener('click', ()=>{}, true);
结语
如有错误,欢迎指出