事件冒泡和事件捕获是分别由IE和Netscape开发团队提出的几乎完全相反的事件流方案。事件流描述了页面接收事件的顺序。
事件冒泡
-
IE事件流被称为事件冒泡,这是因为事件被定义为从具体的元素(文档树中最深的节点)开始触发,然后向上传播至没有那么具体的元素(文档)。
-
比如有如下HTML页面:
<!DOCTYPE html> <html> <head> <title>Event Bubbling Example</title> </head> <body> <div id="myDiv">Click Me</div> </body> </html>在点击页面中的
<div>元素后,click事件会以如下顺序发生:
(1)<div>
(2)<body>
(3)<html>
(4)document
事件捕获
- Netscape团队提出了另一种名为事件捕获的事件流。事件捕获的意思是最不具体的节点应该最先收到事件,而最具体的节点应该最后收到事件。如果前面的例子使用事件捕获,则点击
<div>元素会以下列顺序触发click事件:
(1)document
(2)<html>
(3)<body>
(4)<div>
DOM事件标准
- 2002年,W3C发布标准,文档命名为DOM Level 2 Events Specification,规定浏览器应该同时支持两种调用顺序。
- DOM事件标准描述了事件传播的三个阶段:
- 捕获阶段 — 事件(从Window)向下走近元素。
- 目标阶段 — 事件到达目标元素。
- 冒泡阶段 — 事件从元素上冒泡。
如图下例子:点击 ,事件首先通过祖先链向下到达元素(捕获阶段),然后到达目标(目标阶段),最后上升(冒泡阶段),在途中调用处理程序(有处理程序就执行,没有就跳过)。
addEventListener绑定事件API
-
addEventListener有三个参数element.addEventListener(event, function, useCapture)event:(必需)事件名,支持所有DOM事件。function: (必需)指定要事件触发时执行的函数。useCapture: (可选)指定事件是否在捕获或冒泡阶段执行。true,捕获;falsy值,冒泡;默认值为false。
-
通过
addEventListener添加的事件处理程序只能使用removeEventListener并传入与添加时同样的参数来移除。let btn = document.getElementById("myBtn") //不能移除 btn.addEventListener("click", () => { console.log(this.id) }, false); //可以移除 let handler = function() { console.log(this.id) } btn.addEventListener('click', handler, false); btn.removeEventListener('click', handler, false);
例子与小结
-
栗子1
<div id="box1"> <div id="box2"> <div id="box3">你好</div> </div> </div> <script> box1.onclick = function () { console.log("box1 51561"); }; box2.onclick = function () { console.log("box2"); }; box3.onclick = function () { console.log("box3"); }; box1.onclick = function () { console.log("box1"); }; box1.addEventListener("click",function () { console.log("box1 捕获阶段"); },true); box2.addEventListener("click",function () { console.log("box2 捕获阶段"); },true); box1.addEventListener("click",function () { console.log("box1 冒泡阶段"); },false); box2.addEventListener("click",function () { console.log("box2 冒泡阶段"); },false); box3.addEventListener("click",function () { console.log("box3 冒泡阶段"); },false); box3.addEventListener("click",function () { console.log("box3 捕获阶段"); },true); </script> //控制台输出什么? //box1 捕获阶段 //box2 捕获阶段 //box3 捕获阶段 //box3 //box3 冒泡阶段 //box2 //box2 冒泡阶段 //box1 //box1 冒泡阶段 -
栗子2
<div id="div1"> <button>点击我</button> </div> <script> div1.addEventListener("click",() => { console.log("div clicked!"); },true); let btn = document.querySelector("button"); btn.addEventListener("click",() => { console.log("button clicked!"); }); document.addEventListener("click",() => { console.log("document clicked!"); },true); </script> //控制台输出什么? //document clicked! //div clicked! //button clicked! //先捕获再冒泡 -
栗子3
<div class="grandfather"> <div class="father"> <div class="son">文字</div> </div> </div> // 给三个div分别添加事件监听fnYe/fnBa/fnEr- 提问1: 点击了谁? 点击文字,算不算点击儿子?点击文字,算不算点击爸爸?点击文字,算不算点击爷爷?(答案:都算。)
- 提问2: 点击文字,最先调用fnYe/fnBa/fnEr中的哪一个函数?(答案:看开发者自己选择把fnYe/fnBa/fnEr放在捕获阶段还是放在冒泡阶段。)