DOM事件机制和事件委托

208 阅读4分钟

DOM事件机制是什么?

简单来说DOM事件机制就是为了解决下面这类问题

 <div class = "grandfather">
    <div class = "father">
      <div class="son">
        文字内容
      </div>
    </div>
  </div>

在上面的HTML代码中,我们分别给三个divt添加上点击事件监听fnG,fnF,fnS。那么问题来了,

1.点击文字,,点击了谁?算不算点击son? 算不算点击father?算不算点击grandfather?

答案:都算

2.点击文字,调用顺序?先调用fnG/fnF/fnS中哪个函数监听?

答案:都行,当时IE5认为应该先调用fnS,而网景浏览器则认为应该先调用fnG。

2002年,w3c制定标准,文档名为DOM Level2 Event Specification,认为浏览器应该同时支持两种调用顺序。

首先按照fnG-->fnF-->fnS的顺序来检查监听

然后按照fnS-->fnF-->fnG的顺序来检查监听

有监听函数就执行,并提供事件信息,没有就跳过。

专业术语就是:

从外向内找监听,就叫事件捕获。

从内向外找监听,就叫事件冒泡。

以上就是我们所称的DOM事件机制。

值得注意的是,这些函数并不是调用两次,而是开发者自己可以选择在哪个阶段执行函数监听,即在添加事件监听时通过不同传参来确定。

下面放一张示图来帮助理解事件捕获和事件冒泡。

添加事件监听的具体API: addEVentListener

father.addEventListener('onclick',fn)  //IE5,冒泡

father.addEventListener('click',fn)   //网景,捕获

father.addEventListener('click',fn,bool)
//w3c,如果bool不传或者为falsy值,让fn在冒泡阶段执行;
//如果bool为true则让fn在捕获阶段执行

如上API,开发者可以自己决定将监听函数fn放在事件捕获阶段还是事件冒泡阶段,从而实现不同用户需求。

事件信息event和target,currentTarget

father.addEventListener('click',(e)=>{
    const t = e.currentTarget();
    settimeout(()=>{
       t.classList.add('x')
    },1000)
});

上面这一块代码是给class为father的div添加监听的具体实现。函数大概就是1秒后给当前事件目标加一个x的类名。当我们添加监听事件时,浏览器会返回我们整个事件信息event,这里就是e这个变量,它携带的就是当前事件信息。

e.target(); 是指用户操作的元素

e.currentTarget();是得到开发者当前监听的元素

举例说明:div> span{文字};当监听div而点击文字时,e.target是文字,e.currentTarget是div。

注意点:在这里的this,是e.currentTarget();

特例:

1)当只有一个div被监听时(不考虑父子同时被监听)

e.target和e.currentarget是同一个对象。

2)与此同时,当div分别在捕获阶段和冒泡阶段设置监听函数fn,执行顺序又是如何? 

      执行顺序按照设置顺序来定,也就是说哪个监听函数写在前面,哪个就先执行。

e的生命周期

e在每次监听函数执行之后会发生变化,携带当前事件信息已经不再准确,比如currentTarget会被清掉。所以不应该将其作为标识变量来使用。可以在监听函数里用一个变量保存当前值来使用。

取消冒泡

DOM事件中,捕获不可取消,但是冒泡可以。

e.stopPropagation()可以中断冒泡,浏览器不再向上传递事件。

一般用于封装某些独立的控件。

这里也有特例:有些事件不能阻止默认动作。

比如scroll event,查看MDN文档,我们可以发现它有这两个属性Bubbles 和Cancelable;

Bubbles表示该事件是否冒泡;Cancellable表示是否支持开发者取消冒泡;而scroll事件就不支持取消冒泡。

自定义事件

浏览器自带100多个事件,在MDN文档即可查看。

于此同时,开发者还可以自己自定义事件。代码示例如下,实现点击sonDIV事执行alice事件函数,函数内容打印出‘alice’。

let sonDIV = document.querySelector('.son');

//创建一个事件对象
const eventNew = new CustomEvent('alice',{'detail':{'name':'amiee','age':18}});

//将点击事件分发到新建的事件eventNew中去
sonDIV.addEventListener('click',()=>{
  sonDIV.dispatchEvent(eventNew);});
//设置eventNew事件监听,并实现函数内容
sonDIV.addEventListener('alice',(e)=>{
  console.log('alice')

})

事件委托

事件委托,又称事件代理。顾名思义,“事件委托”即是把原本需要绑定在子元素的响应事件(click、keydown......)委托给父级元素,让其担当事件监听的职务。具体操作就是将监听事件及函数加在父级元素上面。

需求场景,举例说明:

1.当需要给100个按钮加点击事件,如何处理更方便?

答:监听其祖先元素,当其冒泡时,我们再通过判断target来做相应处理。

2.当页面是动态生成一些按钮,但是你需求提前对它们做好监听,如何处理?

答:监听其祖先元素,在监听事件里面再判断是否是我需要监听的元素。

由此可以看出事件委托的优点:1)省监听数(即省内存) 2)可以动态监听