什么是js中的事件流以及事件模型?

我正在参与掘金创作者训练营第4期,点击了解活动详情,一起学习吧!

一、事件

在了解什么是js中的事件流之前,我们先了解一下什么是js的事件。引用W3c的解释

HTML事件就是发生在HTML元素上的事情

当在HTML中使用javaScript时,javaScript能够应对这些事件

举例几个常用的事件:

  • onClick (鼠标单击元素)
  • onmouseover (鼠标移入元素)
  • onmouseout (鼠标移出元素)
  • onkeydown (按下键盘按键)
  • ...

二、事件流

知道了什么是事件,那什么是事件流呢?

我们先从字面意义上理解,事件我们已经知道了是什么,那呢?我们看看百度对于的解释

image.png

那连着事件我们是不是就能将事件流理解为从页面接收事件的顺序,这些事件连起来就形成了一个像液体一样的整体,这个整体中的事件又有着自己的执行顺序,这就是事件流。

三、事件流模型

在事件流中又有着两个模型

  • 事件捕获
  • 事件冒泡

这里我们引用一张图,以便于理解事件流模型

image.png

事件冒泡

当节点事件被触发时,会由内圈到外圈 div-->body-->html-->document 依次触发其祖先节点的同类型事件,直到DOM根节点

事件捕获

当节点事件被触发时,会从DOM根节点开始,依次触发其祖先节点的同类型事件,直到当前节点自身。由外圈到内圈 document-->html-->body-->div

四、事件流模型发展史

事件冒泡是由IE提出的,而事件捕获则是由Netscape(网景)提出的事件流概念。

后来ECMAScript将两种模型进行了整合,制定了统一的标准:先捕获在冒泡

image.png

现在整合后的标准事件流就有了三个阶段:

  1. 事件捕获阶段(目标在捕获阶段不接收事件)
  2. 目标阶段 (事件的执行阶段,此阶段会被归入冒泡阶段)
  3. 事件冒泡阶段 (事件传回Dom根节点)

Tips: DOM2级事件规定了在捕获阶段不会涉及到目标阶段事件,但在IE9、Safari、Chrome、Firefox和Opera9.5及更高版本都会在捕获阶段触发目标事件上的事件

五、addEventListener()

addEventListener() 方法用于向指定元素添加事件句柄。

参数 描述
event 必须,字符串,指定事件名
function 必须,指定事件触发时要执行的函数
useCapture 可选值,指定事件是否在捕获或者冒泡阶段执行;1、true:事件句柄在捕获阶段执行;2、- false- 默认值,事件句柄在冒泡阶段执行

addEventListener()捕获演示

<div id="btn1" style="height: 150px;width: 150px;background: red;color: #fff">
    btn1
    <div id="btn2" style="height: 100px;width: 100px;background: green;color: #fff">
        btn2
        <div id="btn3" style="height: 50px;width: 50px;background: blue;color: #fff">
            btn3
        </div>
    </div>
</div>

<script>
  let btn1 = document.getElementById('btn1')
  let btn2 = document.getElementById('btn2')
  let btn3 = document.getElementById('btn3')
  btn1.addEventListener('click',function (){
    alert('btn1')
  },true)
  btn2.addEventListener('click',function (){
    alert('btn2')
  },true)
  btn3.addEventListener('click',function (){
    alert('btn3')
  },true)
</script>
复制代码

image.png

当我们点击btn3时让我们看看这段代码的执行情况

btn3.gif

当我们点击btn2时让我们看看这段代码的执行情况

btn2.gif

因为addEventListener()最后一个参数我们设置的true,所以整个执行过程按捕获进行。从当前的Dom根节点开始,直到当前节点自身。

addEventListener()冒泡演示

还是同样的代码,只是这里我们将addEventListener()的最后一个参数改为false,将执行过程设置为冒泡。

Tips:因为addEventListener()最后一个参数默认就是false(冒泡),所以不写也可以

<div id="btn1" style="height: 150px;width: 150px;background: red;color: #fff">
    btn1
    <div id="btn2" style="height: 100px;width: 100px;background: green;color: #fff">
        btn2
        <div id="btn3" style="height: 50px;width: 50px;background: blue;color: #fff">btn3</div>
    </div>
</div>

<script>
  let btn1 = document.getElementById('btn1')
  let btn2 = document.getElementById('btn2')
  let btn3 = document.getElementById('btn3')
  btn1.addEventListener('click',function (){
    alert('btn1')
  },false)
  btn2.addEventListener('click',function (){
    alert('btn2')
  },false)
  btn3.addEventListener('click',function (){
    alert('btn3')
  },false)
复制代码

看一下执行情况

冒泡.gif

这里能看到执行顺序是先执行节点自身事件,而后向上执行其祖先节点的同类型事件,直到Dom根节点。

六、stopPropagation()阻止捕获

stopPropagation() 方法防止调用相同事件的传播。

传播意味着向上冒泡到父元素或向下捕获到子元素。

我们在btn2上使用stopPropagation()函数

<div id="btn1" style="height: 150px;width: 150px;background: red;color: #fff">
    btn1
    <div id="btn2" style="height: 100px;width: 100px;background: green;color: #fff">
        btn2
        <div id="btn3" style="height: 50px;width: 50px;background: blue;color: #fff">btn3</div>
    </div>
</div>

<script>
  let btn1 = document.getElementById('btn1')
  let btn2 = document.getElementById('btn2')
  let btn3 = document.getElementById('btn3')
  btn1.addEventListener('click',function (){
    alert('btn1')
  },false)
  btn2.addEventListener('click',function (event){
    event.stopPropagation()
    alert('btn2')
  },false)
  btn3.addEventListener('click',function (){
    alert('btn3')
  },false)
</script>
复制代码

看一下执行情况

阻止.gif

可以看到我们在点击btn3时冒泡执行至btn2后就进行了终止

七、事件流模型的应用

事件委托 又叫 事件代理,指的是利用事件冒泡原理,只需给外层父容器添加事件,若内层子元素有点击事件,则会冒泡到父容器上,这就是事件委托,简单说就是:子元素委托它们的父级代为执行事件。

这种情况的应用场景在什么地方呢?举个例子:

<ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
</ul>
复制代码

我们现在有一个这样的列表,我想监听所有的<li>标签,<li>标签我这里只列了五个,但实际业务中这里有可能会循环出成千上万个<li>标签。如果我们给每个<li>都绑定事件,会极大的影响页面性能,这个时候我们就可以使用事件委托来进行优化。

let ulDom = document.getElementsByTagName('ul')
ulDom[0].addEventListener('click', function(event){
    alert(event.target.innerHTML)
})
复制代码

我们看一下执行情况

委托.gif

可以看到,我们通过事件委托给每一个<li>标签都添加了点击事件

事件委托的优点

我们总结一下事件委托的优化:

  • 提高性能:每一个函数都会占用内存空间,只需添加一个事件处理程序代理所有事件,所占用的内存空间更少。
  • 动态监听:使用事件委托可以自动绑定动态添加的元素,即新增的节点不需要主动添加也可以一样具有和其他元素一样的事件。

おすすめ

転載: juejin.im/post/7068978056618049566