DOM事件模型

266 阅读7分钟

事件捕获和事件冒泡

事件捕获跟冒泡的原理很简单

html代码如下:<div><p></p></div>,假设我现在同时在div跟p元素上面设置了事件监听函数,那么点击之后到底会先执行哪个函数呢?

微软说,当然是从下往上执行啦。先执行p后执行div----这就是事件冒泡

网景说,当然是从上往下啦。先执行div后执行p----这就是事件捕获

到最后w3c组织出来将两者的方案融合了一下,说先从上到下,后从下到上执行顺序来执行事件监听函数,但是为了避免重复执行监听函数,于是提出使用监听函数绑定的形式来使开发者自行选择绑定在哪个阶段。

这就产生了一个事件监听函数addEventListener

监听函数

有三种方式可以让事件绑定监听函数

html的on-属性

这种方式是直接写在html标签上的,例如

<div onclick="console.log('触发事件')">

由于这种方法违背代码分离原则,这里不过多介绍,反正没人用。摈弃糟粕

js的事件属性

	div.onclick =function(){console.log(123)}

使用这种方式来绑定监听函数,有两个缺点:

1、只能在绑定在冒泡阶段

2、如果同一个对象绑定两次同样的事件,即使监听函数不同,也会覆盖

所以我们了解即可

addEventListener

推荐使用这种方式来绑定事件监听函数。它有三个参数:

addEventListener(eventType,Listener[,useCapture)

eventType:事件名称,例如click鼠标点击事件、mousedown鼠标按下事件等

Listener:监听函数。接收一个对象或者一个函数,用于当事件发生时,会执行怎样的函数代码。

useCapture:这个参数接收布尔值或者对象,用于决定是否使用捕获阶段触发,是否只触发一次,listener能否被阻止等

我们需要注意两点:

第二个参数除了函数,还可以是一个包含handleEvent的对象

div.addEventListener('click', {
  handleEvent: function (event) {
    console.log('click');
  }
})

第三个参数可以是布尔值,它会控制listener绑定在冒泡阶段还是捕获阶段。默认是false,如果写true,就表示绑定在捕获阶段。

第三个参数还可以是一个配置对象,该对象有以下属性:

  • capture:布尔值,表示该事件是否在捕获阶段触发监听函数。

  • once:布尔值,表示监听函数是否只触发一次,然后就自动移除。

  • passive:布尔值,表示监听函数不会调用事件的preventDefault方法。如果监听函数调用了,浏览器将忽略这个要求,并在监控台输出一行警告。这个参数主要是事先告诉浏览器不阻止事件的默认方法,提高浏览器的流畅度

如果只希望监听函数执行一次,那么可以在第三个参数传:{once:true}

event对象

当listener函数执行时,会生成一个event对象,这个参数会包含事件的各种信息, 我们可以将它作为参数传递给监听函数。

浏览器原生提供event对象,我们也可以自己创造一个event对象来帮助理解浏览器自带的event对象的构造。

let newEvent=new Event(type,options)

新的event对象通过Event这个构造函数产生,构造函数需要两个参数:

  • type指的是事件名称,就想click一样,我们可以造一个,假设为qiuyanxi
  • options是一个配置项的对象,主要有两个属性:

bubbles:布尔值,可选,默认为false,表示事件对象是否冒泡。

cancelable:布尔值,可选,默认为false,表示事件是否可以被取消,即能否用Event.preventDefault()取消这个事件。一旦事件被取消,就好像从来没有发生过,不会触发浏览器对该事件的默认行为。

新建完后,可以使用dispatchEvent方法触发该事件。

我们实际操作一下,假设我现在有两个元素<div><p></p></div>

我们让div绑定一个新生成的事件,但是由p元素去触发这个事件。

      let p =document.querySelector('p')
      let div=document.querySelector('div')
      div.addEventListener('qiuyanxi',function(){
      		console.log('此时qiuyanxi事件执行')},false)
      let newEvent =new Event('qiuyanxi',{
         	 bubbles:false,cancelable:false}) //事件对象不冒泡,事件默认不被取消
      p.dispatchEvent(newEvent)

上面的代码由p触发事件,那么由于我在绑定时绑定事件在冒泡阶段,而我的事件对象又不支持冒泡阶段,所以 console.log('此时qiuyanxi事件执行')并不会发生。

假设改成bubbles:true,则允许绑定在冒泡阶段。

如果把绑定监听函数修改成div.addEventListener('qiuyanxi',主要看第三个参数,true),将监听函数绑定在捕获阶段,那么我就可以不修改bubbles:false,也是可以执行的。

我们通过结论可以发现,事件对象跟事件监听函数绑定需要有对应,否则无法触发事件对象。

一般来说大部分浏览器生成的事件对象都是支持冒泡的。

事件委托

由于存在事件冒泡和事件捕获,事件总是会影响到子元素身上。所以可以通过事件委托来使子元素的事件监听函数放到祖先元素身上。

由于event对象记录了事件发生时的信息,我们可以通过e.target知道到底点击了谁。

    /*html代码是<div>
      <p>1</p>
      <p>2</p>
      <p>3</p>
    </div>*/
//--------------------------
    let div=document.querySelector('div')
    div.addEventListener('click',function(e){
      if(e.target.innerText ==='1'){
        console.log(1)
      }else if(e.target.innerText === '2'){
        console.log(2)
      }else{
        console.log(3)
      }
    })

this指向

      div.addEventListener('click',function(e){
          console.log(e),
          console.log(e.targer),
          console.log(e.currentTarget)
          console.log(this)
      })

在浏览器中,我们执行这样一段代码,可以清楚看到event的构造,这里主要说两个点,e.currentTarget指向的是绑定事件的对象,也就是div,而e.target则是触发事件的对象,它也许不是绑定事件的对象。

this指向的是e.currentEvent,建议写事件监听函数时,使用箭头函数,配合e.currentTarget来避免this指向不清的问题。

event.stopPropagation 阻止传播

假设,我们现在有三个元素:

<section><div><span></span></div></section>,

如果我们将其都绑定事件监听函数在冒泡阶段,当我们执行到第二个元素也就是span时,不想继续让浏览器往上冒泡,那么就可以使用e.stopPropagation()来取消冒泡。

反过来说,如果将监听函数绑定在捕获阶段,同样也可以使用这个方法来阻止传播

Event.stopImmediatePropagation

这个方法比stopProragation更加有效,如果同一个节点对于同一个事件指定了多个监听函数,这些函数会根据添加的顺序依次调用。只要其中有一个监听函数调用了Event.stopImmediatePropagation方法,其他的监听函数就不会再执行了。

/*
html代码:
<section>1<div>2<span>3</span></div></section>
*/

let section=document.querySelector('section')
let div=document.querySelector('div')
let span=document.querySelector('span')

section.addEventListener('click',(e)=>{
  console.log(1)
  e.stopImmediatePropagation() //调用了这个方法,那么下面的1.1也不会执行了
},true)

section.addEventListener('click',(e)=>{
  console.log(1.1)
  
},true)

div.addEventListener('click',(e)=>{
  console.log(2)
},true)

span.addEventListener('click',(e)=>{
  console.log(3)
},true)

removeEventListener

我们可以使用eventTarget.removeEventListener来移除由addEventListener绑定的事件

removeEventListener方法的参数,与addEventListener方法完全一致。

注意,使用removeEventListener移除的函数必须与add时的一致,就连参数都要保持一致

EventTarget.dispatchEvent()

这个函数的参数是一个event对象实例

一般如果我们创建自定义的事件时,可以用到它

let newEvent = new Event('xxx')
eventTarget.dispatchEvent(newEvent)

event.preventDefault

Event.preventDefault方法取消浏览器对当前事件的默认行为。比如点击链接后,浏览器默认会跳转到另一个页面,使用这个方法以后,就不会跳转了;再比如,按一下空格键,页面向下滚动一段距离,使用这个方法以后也不会滚动了。

该方法生效的前提是,事件对象的cancelable属性为true,如果为false,调用该方法没有任何效果。

注意,该方法只是取消事件对当前元素的默认影响,不会阻止事件的传播。如果要阻止传播,可以使用stopPropagation()或stopImmediatePropagation()方法。