JavaScript事件流和事件委托

138 阅读4分钟

JavaScript事件流和事件委托

1.事件传播机制(V模型)

想要深入了解委托,最好先理解事件传播机制:

我们把事件分为三个阶段:捕获阶段目标阶段冒泡阶段;

1 捕获阶段(捕获你要操作的目标元素)

​ 当点击时,先经过捕获阶段,从最外层如html(这里不考虑兼容了)层层找到目标。

2 目标阶段:

​ 经过目标阶段,响应事件。

3 冒泡阶段

​ 从点击目标往外层层触发相同的事件方法直到最外层(根节点)。

2.关于事件绑定

事件绑定,有DOM0级(on+type)和DOM2级(addEventListener

准备一个盒子

<div id='box'>
  
</div>

dom0:在属性上挂载,同一个元素只能有一个点击事件,多个点击事件,后者会覆盖前者;

const divEl = document.querySelector("#box");

divEl.onclick = function(){
  console.log(1);
}
divEl.onclick = function(){
  console.log(2);
}

【点击这个元素打印的是2,前一个被后一个覆盖了】

dom2:在EventTarget.prototype定义的,同一个元素多个点击事件不会覆盖,都会执行

【原理是有一个统一的事件池;触发时,浏览器会把事件池中所有的按照存放顺序发放】

const divEl = document.querySelector("#box");

divEl.addEventListener("click",function(e){
  console.log(1)
})

divEl.addEventListener("click",function(e){
  console.log(2)
})

【点击这个box元素,1,2都会被打印的】

3.写一个事件冒泡

使用DOM0级事件绑冒泡

<div id='box1'>1
   <div id='box2'>2
      <div id='box3'>3</div>
   </div>
</div>
const divEl1 = document.querySelector("#box1");
const divEl2= document.querySelector("#box2");
const divEl3= document.querySelector("#box3");


divEl1.onclick = function(){
  console.log(1);
}
divEl2.onclick = function(){
  console.log(2);
}
divEl3.onclick = function(){
  console.log(3);
}

当我们点击最内层的3号盒子,你发现绑定在外面的盒子的方法也被调用了,这个就是这个事件冒泡执行了

使用DOM1级事件绑冒泡

divEl1.addEventListener("click",function(e){
  console.log(1)
},false)

divEl2.addEventListener("click",function(e){
  console.log(2)
},false)
divEl3.addEventListener("click",function(e){
  console.log(3)
},false)

输出跟上面是一样的,因为我们绑定在了冒泡阶段。(true捕获,false冒泡)

4.写一个事件捕获

【因为`Dom0`级事件,只支持事件冒泡,所以只能用`DOM1`级事件绑定写一个事件捕获】

divEl1.addEventListener("click",function(e){
  console.log(1)
},true)

divEl2.addEventListener("click",function(e){
  console.log(2)
},true)
divEl3.addEventListener("click",function(e){
  console.log(3)
},true)

同样点击最里面的盒子

image-20210327004455171

你会发现,先执行的最外层盒子,很容易理解啊,因为捕获是从外层开始找目标元素的。

5.验证捕获和冒泡的执行顺序

我们给上面的元素,使用DOM2级事件绑定同时绑定冒泡和捕获事件

const divEl1 = document.querySelector("#box1");
const divEl2= document.querySelector("#box2");
const divEl3= document.querySelector("#box3");

divEl1.addEventListener("click",function(e){
  console.log(1,"捕获阶段")
},true)

divEl2.addEventListener("click",function(e){
  console.log(2,"捕获阶段")
},true)
divEl3.addEventListener("click",function(e){
  console.log(3,"捕获阶段")
},true)

divEl1.addEventListener("click",function(e){
  console.log(1,"冒泡阶段")
},false)

divEl2.addEventListener("click",function(e){
  console.log(2,"冒泡阶段")
},false)
divEl3.addEventListener("click",function(e){
  console.log(3,"冒泡阶段")
},false)

点击内部的3号盒子,符合我们的预期,先执行捕获这个元素,然后执行目标元素的方法,然后冒泡出去!

6.目标元素的冒泡和捕获顺序

上面我们点击的都是最内部的3号元素,也是我们的目标元素,也是我们V模型的转折点,这里的冒泡和捕获感觉可以以任何顺序执行,我们来验证一下!我们换一下执行顺序看看

image-20210327005616242

divEl1.addEventListener("click",function(e){
  console.log(1,"捕获阶段")
},true)

divEl2.addEventListener("click",function(e){
  console.log(2,"捕获阶段")
},true)

//这里我们把目标元素的冒泡阶段写在前面,这样js执行主线程会先执行这里
divEl3.addEventListener("click",function(e){
  console.log(3,"冒泡阶段")
},false)
divEl3.addEventListener("click",function(e){
  console.log(3,"捕获阶段")
},true)
//==========================================================

divEl1.addEventListener("click",function(e){
  console.log(1,"冒泡阶段")
},false)

divEl2.addEventListener("click",function(e){
  console.log(2,"冒泡阶段")
},false)

我们看一下执行的结果把

image-20210327005931614

可以看出来!目标元素的冒泡捕获,和代码执行的顺序有关系的

7.关于事件委托

【那什么叫事件委托呢?】

顾名思义!就是把事件委托给别人执行,那么在dom树中,我们只能理解为,把应该在自身上面触发的事件,委托给别的dom元素,那么怎么做到的呢?就是使用事件冒泡。

【事件委托的常用场景】

因为在页面中,绑定事件越多,浏览器内存占用越大,严重影响性能 。比如我们100条数据,循环出来100个li标签去渲染,然后对每个li标签还要添加一些操作事件,这样的话占用内存很大,

<div id='box1'>
</div>
const divEl1 = document.querySelector("#box1");

const data = 100;

for(let i = 0;i < data;i++){
  const li = document.createElement("li");
  li.innerHTML = i;
  li.addEventListener("click",function(){
      console.log(li.innerHTML);
  })
  divEl1.appendChild(li)
}

所以我们一般使用事件委托,利用冒泡机制,委托给他的父级元素。

const divEl1 = document.querySelector("#box1");

const data = 100;

for (let i = 0; i < data; i++) {
    const li = document.createElement("li");
    li.innerHTML = i;
    divEl1.appendChild(li)
}
//绑定给他的父元素
divEl1.addEventListener("click", function (e) {
    console.log(e.target.innerHTML);
})

image-20210327095752543

一样可以完成需求!

8.委托的局限性

比如 focusblur 之类的事件本身没有事件冒泡机制,所以无法委托;

2 mousemovemouseout这样的事件,虽然有事件冒泡,但是只能不断通过位置去计算定位,

9.使用委托的注意项(可以叫应用项)

1 只在必须的地方,使用事件委托,比如:ajax的局部刷新区域

2 尽量的减少绑定的层级,并且不在body元素上,进行绑定;(事件委托的原理离不开DOM的

3 减少绑定的次数,如果可以,那么把多个事件的绑定,合并到一次事件委托中去,由这个事件委托的回调,来进行分发。

img

writeBy:村望 部分资源来自互联网