详解浏览器事件捕捉,冒泡
浏览器事件模型中的过程次要分为三个阶段:捕捉阶段、指标阶段、冒泡阶段
<div id="parent" class="flex-center">
parent
<p id="child" class="flex-center">
child
<span id='son' class="flex-center">
son
<a href="https://www.baidu.com" id="a-baidu"> 点我啊 </a>
</span>
</p>
</div>
const parent = document.getElementById("parent");
const child = document.getElementById("child");
const son = document.getElementById("son");
window.addEventListener("click", function (e) {
// e.target.nodeName 指以后点击的元素, e.currentTarget.nodeName 绑定监听事件的元素
console.log("window 捕捉", e.target.nodeName, e.currentTarget.nodeName);
}, true);
parent.addEventListener("click", function (e) {// e.stopPropagation();
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);
window.addEventListener("click", function (e) {console.log("window 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
parent.addEventListener("click", function (e) {console.log("parent 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
child.addEventListener("click", function (e) {console.log("child 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
son.addEventListener("click", function (e) {console.log("son 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
第三个参数
这里要留神 addEventListener 的第三个参数, 如果为 true,就是代表在捕捉阶段执行。如果为 false,就是在冒泡阶段进行
阻止事件流传
- e.stopPropagation()
大家常听到的是阻止冒泡,实际上这个办法不止能阻止事件冒泡,还能阻止捕捉阶段的流传 - stopImmediatePropagation()
如果有多个雷同类型事件的事件监听函数绑定到同一个元素,当该类型的事件触发时,它们会依照被增加的程序执行。如果其中某个监听函数执行了 event.stopImmediatePropagation() 办法,则以后元素剩下的监听函数将不会被执行。
场景设计:
先有一个页面,这个页面上有许多元素,div p button
每个元素上都有本人的 click 事件,都不雷同;
当初来了一个新的需要,一个用户进入这个页面的时候,会有一个状态 banned, window.banned.
true: 以后用户被封禁了,点击任何元素,都不执行现有 click,而是提醒被封禁了
false:不作任何操作。
window.addEventListener("click", function (e) {if(window.banned){e.stopPropagation();
alert('你被封禁了');
return;
}
// ...
}, true);
阻止默认行为
- e.preventDefault()
e.preventDefault() 能够阻止事件的默认行为产生,默认行为是指:点击 a 标签就转跳到其余页面、拖拽一个图片到浏览器会主动关上、点击表单的提交按钮会提交表单等等,因为有的时候咱们并不心愿产生这些事件,所以须要阻止默认行为
兼容性
attachEvent——兼容:IE7、IE8;不反对第三个参数来管制在哪个阶段产生,默认是绑定在冒泡阶段
addEventListener——兼容:firefox、chrome、IE、safari、opera;
// 兼容写法
class BomEvent{constructor(element){this.element = element;}
addEvent(type, handler){if(this.element.addEventListener){this.element.addEventListener(type, handler, false); // IE 不反对捕捉
}else if(this.element.attachEvent){this.element.attachEvent(`on${type}`, handler)
}else{this.element[`on${type}`] = handler;
}
}
removeEvent(){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(ev){if(ev.stopPropagation){ev.stopPropagation(); // W3C
}else{ev.cancelBubble = true; // IE}
}
// 阻止默认行为
function preventDefault(ev){if(ev.preventDefault){ev.preventDefault()
}else{ev.returnValue = false;}
}
绑定事件的使用,以及封装一个多浏览器兼容的绑定事件函数
大家常见的一个面试题可能是 ul + li,点击每个 li alert 对应的索引,这里就给大家来写一下看看
- 先来给每个 li 绑定事件
-
再来写一个事件委托的形式
<ul id="ul"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <!-- ... --> </ul>
// const liList = document.getElementsByTagName("li"); // for(let i = 0; i<liList.length; i++){// liList[i].addEventListener('click', function(e){// alert(` 内容为 ${e.target.innerHTML}, 索引为 ${i}`); // }) // } const ul = document.querySelector("ul"); ul.addEventListener('click', function(e) { const target = e.target; if(target.tagName.toLowerCase() === 'li'){const lists = document.querySelector("li"); const index = Array.from(lists).indexOf(target); // Array.prototype.indexOf.call(lists, target); alert(` 内容为 ${target.innerHTML}, 索引为 ${index}`); } })