Skip to content
On this page

事件机制

事件机制拥有三个阶段,分别是:事件捕获处于目标事件冒泡
需要注意的是IE是没有事件捕获这一阶段,具体原因可自行查阅。

事件执行的时候,我们整个执行流程会先去捕获再进行冒泡

  • 捕获:
    会从Document对象去向下传播事件,直到找到目标元素,才会停止传播。
  • 冒泡:
    会从目标元素向上传播事件,直到Document对象,才会停止传播。

捕获

Document -> DIV -> Target -> DIV -> Document

冒泡

> Target 前后区分捕获和冒泡

假设我们拥有下列的DOM结构,我们去依次分析事件捕获和事件冒泡,再去了解事件委托。

html
<ul>
  <li>
    <span>
      <a></a>
    </span>  
  </li>
</ul>  

事件捕获

由网景最先提出,事件会从最外层开始触法,直到最具体的元素。

如上列DOM结构,假设我们的ulspan都绑定了点击事件,span没有脱离文档流被被ul包含,那么这时候点击span会先触发ul的点击事件,然后再触发span的点击事件。

js

ul.addEventListener('click'() => {
  console.log('ul')
}true)

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

// log执行顺序
// ul => span

可能我们不希望触法子元素的事件,这时候我们可以使用stopImmediatePropagation()方法来阻止事件捕获。

关于stopImmediatePropagation方法的详细描述请查阅MDN

js

ul.addEventListener('click'(e) => {
  e.stopImmediatePropagation()
  console.log('ul')
}true)

// log 将不会打印 span

总得来说就是事件捕获就是: 优先触发父元素的事件再传递到子元素, 也就是 父 => 子

事件冒泡

由微软提出,事件会从最内层开始触法,直到最外层的元素.

还是使用事件捕获时的例子来进行讲解, ulspan都绑定了点击事件, span没有脱离文档流, 那么这时候我们点击span的时候会和捕获时的执行顺序完全相反, 会优先触法span的点击事件,再触发ul的点击事件。

js

ul.addEventListener('click'() => {
  console.log('ul')
})

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

// log执行顺序
// span => ul

如果我们希望点击span不触法ul的事件,我们可以使用stopPropagation()方法来阻止事件冒泡。

关于stopPropagation方法的详细描述请查阅MDN

js
span.addEventListener('click'(e) => {
  e.stopPropagation()
  console.log('span')
})

// log 将不会打印 ul

总得来说事件冒泡就是: 优先触法子元素的事件再传递到父元素,也就是 子 => 父这么一个流程。

TIP: 上面已经讲述了捕获和冒泡这两种机制和阻止传播的方法。由此可见这两种机制除了执行顺序的不一致,其他的都大差不差.

事件委托

事件委托又称为事件代理,简单来讲就是将元素一整块进行监听,是否触发了制定的事件。

js

ul.addEventListener('click',(e) => {
  const targetName = e.target.nodeName
  console.log(targetName)
  switch (targetName) {
    case "UL":
      
      break;
    ...
    default:
      break;
  }
})

// 根据每次点击到的元素打印: UL || LI || SPAN || A

上述例子,就是简单的事件委托用法,我们可以根据点击到的元素去执行对应的事件,而不需要每个元素都去绑定一个事件。

为什么我们只绑定了ul,却可以获取到lispana这些元素呢。因为我们使用事件委托将ul整个DOM结构都监听了,任何处于ul内部中的元素,都会触发点击事件。

通常我们会在拥有多个子元素且需要绑定相同事件的元素上去使用,例如:

html
<ul>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
</ul>

我们需要做一个tab切换每个li都需要绑定点击事件,我们应该去使用事件委托而不是获取所有li去单独绑定。

js

// 不推荐
const liList = document.querySelectorAll("li")
for (let i = 0; i < liList.length; i++) {
  const element = liList[i];
  element.onClick = () => {
    // TODO
  }
}


// 推荐
const ul = document.querySelector("ul")
ul.addEventListener('click',(e) => {
  if (e.target.nodeName === 'LI') {
    // TODO
  }
})

这么做的好处在于:

  • 我们只需要绑定一次事件,提高效率
  • 动态添加进去的元素也同样拥有事件
  • 不需要管理多个函数 (这里指js引擎,而不是代码上)
  • 减少了内存的消耗 (不需要额外获取dom和执行loop去挨个绑定事件)
  • 减少了代码和DOM之间的关联 (只需要关注父元素,不需要关注内部元素)
  • 修改DOM的时候不需要考虑删除事件 (修改dom的时候不需要思考之前绑定的事件没有删除是否会造成副作用)

TIP: 在vuereact等框架中,我们需要注意在销毁组件时使用removeEventListener删除掉事件监听,否则多次绑定会执行多次,容易造成执行栈溢出。

TODO 待完善

Chasing the Wind!