DOM事件机制

238 阅读3分钟

Javascript与HTML之间的交互是通过事件来实现的。事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。可以使用侦听器来预定事件,以便事件发生时执行相应的代码。

事件流

事件流 是指从页面接收事件的顺序。有意思的是,IE和网景提出不同的事件流概念。IE支持的事件流是事件冒泡,网景公司支持的事件流是事件捕获。

事件冒泡

IE的事件流叫做事件冒泡,即事件由内向外找监听函数。我们来看下面的代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Document</title>
</head>
<body>
  <div id="div1">click Me</div>
</body>
</html>

如果单击了页面的div元素,click事件会按照以下顺序传播:

  • <div>
  • <body>
  • <html>
  • <body>
  • document
    传播的过程是从div到document传播,逐级向上传递。

事件捕获

网景团队的事件流叫事件捕获 ,即事件由外向内找监听函数。以前面的代码为例,监听的顺序是从document向<div>传播。顺序如下:

  • document
  • <html>
  • <body>
  • <div>

DOM事件流

"DOM2级事件"规定的事件流包括三个阶段: 事件捕获阶段,处于目标阶段、事件冒泡阶段 。首先发生的是事件捕获,为截取事件提供机会;然后是实际的目标接收到事件,最后是冒泡阶段。

addEventLister

事件绑定的Api如下:

  • IE5*:baba.attachEvent('onClick',fn)//冒泡
  • 网景:baba.addEventListener('onClick',fn)//捕获
  • w3c:baba.addEventListener('onClick',fn,bool)其中,bool不传或者为falsy值,fn会走冒泡;如果bool为true,fn会走捕获阶段。 接下来,我们来做个事件先捕获再冒泡的小练习吧!
    html代码:
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title></title>
</head>
<body>
<div class="level1 x">
  <div class="level2 x">
    <div class="level3 x">
      <div class="level4 x">
        <div class="level5 x">
          <div class="level6 x">
            <div class="level7 x"></div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
</body>
</html>

css代码:

*{
  box-sizing:border-box;
}
div[class^=level]{
 border:1px solid;
 border-radius:50%;
 display:inline-flex;
}
.level1{
  padding:10px;
  background:purple;
}
.level2{
  padding:10px;
  background:blue;
}
.level3{
padding:10px;
  background:cyan;
}
.level4{
  padding:10px;
  background:green;
}
.level5{
  padding:10px;
  background:yellow;
}
.level6{
  padding:10px;
  background:orange;
}
.level7{
  width:50px;
  height:50px;
  border:1px solid;
  background:red;
  border-radius:50%;
}
.x{
  background:transparent;
}

js代码:

const level1=document.querySelector('.level1')
const level2=document.querySelector('.level2')
const level3=document.querySelector('.level3')
const level4=document.querySelector('.level4')
const level5=document.querySelector('.level5')
const level6=document.querySelector('.level6')
const level7=document.querySelector('.level7')

let n=1;
let fn=((e)=>{
  const t=e.currentTarget
  setTimeout(()=>{
    t.classList.remove('x')
  },n*1000)
  n+=1
})
let fn2=((e)=>{
  const t=e.currentTarget
  setTimeout(()=>{
    t.classList.add('x')
  },n*1000)
  n+=1
})
level1.addEventListener('click',fn,true)
level1.addEventListener('click',fn2)
level2.addEventListener('click',fn,true)
level2.addEventListener('click',fn2)
level3.addEventListener('click',fn,true)
level3.addEventListener('click',fn2)
level4.addEventListener('click',fn,true)
level4.addEventListener('click',fn2)
level5.addEventListener('click',fn,true)
level5.addEventListener('click',fn2)
level6.addEventListener('click',fn,true)
level6.addEventListener('click',fn2)
level7.addEventListener('click',fn,true)
level7.addEventListener('click',fn2)

最后程现的效果如下:


上述实验从而证明了w3c的事件模型:事件先捕获再冒泡。需要注意的是:e对象被传给所有监听函数,事件结束后,e对象就不存在了。

取消冒泡

需要注意的是:冒泡可以取消,而捕获取消不了。用e.stopPropagation()可以中断冒泡。
代码沿用上例,js代码改为:

 level1.addEventListener('click',remove)
 level2.addEventListener('click',remove)
 level3.addEventListener('click',(e)=>{
        e.stopPropagation()
    })
 level4.addEventListener('click',remove)
 level5.addEventListener('click',remove)
 level6.addEventListener('click',remove)
 level7.addEventListener('click',remove)


到level3就停止冒泡了。
target和currentTarget

  • e.target用户操作的元素
  • e.currentTarget开发者监听的元素

事件代理

事件代理 即是把原本需要绑定的事件委托给父元素,让父元素担当事件监听的职务 ,事件代理的原理是DOM元素的事件冒泡,使用事件代理的好处是可以提高性能。 事件代理的使用场景如下:
场景一: 要给100个按钮添加点击事件,怎么办?
答: 监听这100个按钮的祖先,等冒泡的时候判断target是不是这100个按钮中的一个。 代码实现:
html代码:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title></title>
</head>
<body>
<div id="div1">
  <span>span1</span>
  <button>click1</button>
  <button>click2</button>
  <button>click3</button>
  <button>click4</button>
  <button>click5</button>
  <button>click6</button>
  <button>click7</button>
   /*中间为click8-click99*/
  <button>click100</button>
</div>
</body>
</html>

js代码:

div1.addEventListener('click',(e)=>{
  const t=e.target
  if(t.tagName.toLowerCase()==='button'){
    console.log('botton被点击了')
    console.log('button的Id是'+t.textContent)
  }
})

使用场景二:要监听目前不存在的元素的点击事件,怎么办?
答:监听祖先,等点击的时候看看是不是想要监听的元素。
html代码:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title></title>
</head>
<body>
<div id="div1"></div>
</body>
</html>

js代码:

setTimeout(()=>{
const button=document.createElement('button')
button.textContent='click 1'
div1.appendChild(button)
},1000)
div1.addEventListener('click',(e)=>{
  const t=e.target
  if(t.tagName.toLowerCase()==='button'){
   console.log('button 被click')
  }
})