前言知识
在中,我们将事件发生的顺序成为“ 事件流 ”,当我们触发某个事件时,会发生一系列的连锁反应。
但是,如果我们有几个嵌套的元素来处理同一个事件(如下图),到底哪个事件会先被触发呢?所以,我们需要理解事件传播顺序。
微软和网景这两个公司提出了两种不同的概念,分别是事件捕获和事件冒泡。 事件捕获是由微软公司提出的,当鼠标点击或触发DOM事件时(被触发DOM事件的这个元素被叫做事件源),浏览器会从根节点=>事件源(由外层到内层)进行事件传播。 事件冒泡是由网景公司提出的,它的传播顺序与事件捕获相反,是从事件源=>根节点(由内层到外层)进行事件传播。
现在
我们现在使用DOM标准事件流的传播顺序的是W3C统一后的标准—— 先捕获后冒泡 。即当出发DOM事件时,会先进行事件捕获,捕获到事件源之后通过事件传播进行事件冒泡。如下图:
我们还需要了解DOM事件流(event flow)存在 三个阶段 :事件捕获阶段、处于目标阶段、事件冒泡阶段。
下面我们来看事件捕获和事件冒泡是如何出现的
事件捕获(Event Capturing)
事件捕获是由外到内的。在事件捕获阶段,事件会从 DOM 树的最外层开始,依次经过目标节点的各个父节点,并触发父节点上的事件,直至到达事件的目标节点。如下图:
上图的例子中,事件处理顺序是 Window=>Document=>box1=>box2=>a 。除此之外,我们还可以观察到:事件捕获只发生在被点击的元素或目标上,该事件 不会传播到子元素 。
以上内容我们用到了addEventListener方法,在一般情况下,该方法只会用到两个参数,一个是需要绑定的事件,另一个是触发事件后要执行的函数,但是,addEventListener还可以传入第三个参数,该参数默认值是false,表示在事件冒泡阶段调用事件处理函数;如果该参数为true,则表示在事件捕获阶段调用处理函数。
window.addEventListener("click",()=>{
console.log('window');
},true);
document.querySelector("a").addEventListener("click",(e)=>{
console.log("click me");
},true);
//其他元素类似
事件冒泡(Dubbed Bubbling)
上面我们说了事件捕获,事件冒泡和事件捕获是完全相反的。事件冒泡是由内到外的,它是从目标节点开始,沿父节点依次向上,并触发父节点上的事件,直至文档根节点,就像水底的气泡一样,会一直向上。如下图:
document.addEventListener("click",()=>{
console.log('document');
},false);
document.querySelector("a").addEventListener("click",(e)=>{
console.log("click me");
},false);
//其他元素类似
阻止事件捕获和事件冒泡
在我们了解事件捕获和事件冒泡后,难免会思考一个问题:如果我们在某个节点上绑定了一个事件,我们需要在点击时触发这个事件,但是由于事件冒泡,这个节点的事件被它的子元素触发了,我们应该怎么阻止这种情况发生呢?
document.querySelector("a").addEventListener("click",(e)=>{
e.stopPropagation();
console.log("click me");
},false);
事件委托(又名:事件代理)
JavaScript 事件代理(委托) 是一种简单的技巧,把事件处理器添加到一个上级元素上,这样就避免了把事件处理器添加到多个子级元素上.这主要得益于浏览器的事件冒泡机制.
事件委托的核心原理:给父节点添加侦听器,利用事件冒泡影响每一个子节点
<ul>
<li>item1</li>
<li>item2</li>
<li>item3</li>
<li>item4</li>
</ul>
document.querySelector("ul").addEventListener("click",(e)=>{
//to do
},false);
事件委托优点
- 减小内存消耗
使用事件委托减少事件注册,可以大量节省内存
- 动态绑定事件
在删除某个子元素时,不需要同时为删除的元素解绑事件
事件委托缺点
- 事件委托基于冒泡,对于不冒泡的事件不支持
- 层级过多,冒泡过程中,可能会被阻止掉.
- 理论上委托会导致浏览器频繁调用处理函数,虽然很可能不需要处理,所以建议就近委托