优秀的编程知识分享平台

网站首页 > 技术文章 正文

Javascript中的事件模型有哪些?事件委托是什么,如何实现?

nanyue 2024-08-22 17:37:58 技术文章 7 ℃

DOM0事件模型(原始事件模型)
有两种实现方式

  1. 通过元素属性来绑定事件
<body>
  <script>
    // 通过元素属性来绑定事件
    function myFuntion() {
      console.log("触发点击了")
    }
  </script>
  <button onclick="myFuntion()">点我</button>
</body>

2.先获取页面元素,然后以赋值的形式来绑定事件

<body>
  <button id="myClick">点我</button>
  <script>
    // 先获取页面元素,然后以赋值的形式来绑定事件
    const btn = document.getElementById("myClick")
    btn.onclick = function () {
      console.log("触发点击了")
    }
    btn.onclick = null; // 解除事件
  </script>
</body>

一个dom节点只能绑定一个事件,再次绑定将会覆盖之前的事件。

DOM0级事件模型是早期的事件模型,所有的浏览器都是支持的

DOM2事件模型
dom2新增冒泡和捕获的概念,并且支持一个元素节点绑定多个事件

  1. 事件捕获和事件冒泡(capture,bubble )

DOM2 级事件模型共有三个阶段:

  • 事件捕获阶段:事件从 document 向下传播到目标元素,依次检查所有节点是否绑定了监听事件,如果有则执行。
  • 事件处理阶段:事件在达到目标元素时,触发监听事件。
  • 事件冒泡阶段:事件从目标元素冒泡到 document,并且一次检查各个节点是否绑定了监听函数,如果有则执行。

注册事件和解除事件

在DOM2级中使用addEventListener和removeEventListener来注册和解除事件(IE8及之前版本不支持)。这种函数较之之前的方法好处是一个dom对象可以注册多个相同类型的事件,不会发生事件的覆盖,会依次地执行各个事件函数。

addEventListener有三个参数 事件名称、事件回调、捕获/冒泡

<body>
  <button id="myClick">点我</button>
  <script>
    var btn = document.getElementById("myClick")
    btn.addEventListener("click", function() {
      console.log("first")
    }, true)
    btn.addEventListener("click", function() {
      console.log("second")
    }, true)
  </script>
</body>

设置为true,则事件在捕获阶段执行,为false则在冒泡阶段执行。

下面演示如何使用事件流的发生机制

<body>

  <div id="outer">
    <button id="myClick">点我</button>
  </div>

  <script>
    var outer = document.getElementById("outer")
    var btn = document.getElementById("myClick")

    outer.addEventListener("click", function() {
      console.log("outer")
    }, true)

    btn.addEventListener("click", function() {
      console.log("inner")
    }, true)
  </script>
</body>

这段代码,我们使用了捕获事件,由于inner是嵌套在outer中的,那么结果就是outer首先执行,其次是inner执行。
那么我把outer的执行时机改为冒泡的阶段呢?

outer.addEventListener("click", function() {
  console.log("outer")
}, false)  // outer的执行时机改为冒泡的阶段

这种情况下,就是先执行inner后执行outer了

同理我们把二者的事件执行时机都改为冒泡阶段的话,依旧是先执行inner后执行outer。

那么还有个问题,就是如果我们把inner注册两个click事件,一个是在捕获阶段,另一个是在冒泡阶段,也就是说把addEventListenter的第三个参数分别设置为false和true,那么执行的顺序又是怎样的呢。

<body>

  <div id="outer">
    <button id="myClick">点我</button>
  </div>

  <script>
    var btn = document.getElementById("myClick")

    btn.addEventListener("click", function() {
      console.log("inner 捕获")
    }, false)

    btn.addEventListener("click", function() {
      console.log("inner 冒泡")
    }, true)
  </script>

</body>

但是这种结果是与注册的顺序有关系的,先注册就先执行。发现最后具体的dom对象是只有一个的。

如果我们给outer和inner都注册了click事件但是我不希望outer执行怎么办呢?这个时候我们就需要用到stopPropagation函数了,这个函数是用来阻止冒泡

<body>

  <div id="outer">
    <button id="myClick">点我</button>
  </div>

  <script>
    var outer = document.getElementById("outer")
    var btn = document.getElementById("myClick")

    outer.addEventListener("click", function() {
      console.log("outer")
    }, false)

    btn.addEventListener("click", function(event) {
      console.log("inner")
      event.stopPropagation(); // 阻止事件冒泡
    }, false)
    // 这段代码,我们在inner阻止了冒泡事件, 不会触发outer的点击事件
  </script>

</body>

由于事件捕获阶段没有可以阻止事件的函数,所以一般都是设置为事件冒泡。

IE事件模型
IE事件只支持冒泡,所以事件流有两个阶段:

  • 事件处理阶段:事件在达到目标元素时,触发监听事件。
  • 事件冒泡阶段:事件从目标元素冒泡到 document,并且一次检查各个节点是否绑定了监听函数,如果有则执行。
<body>
  <div id="outer">
    <button id="myClick">点我</button>
  </div>

  <script>
    // 该事件模型只在IE中有效
    var btn = document.getElementById("myClick")

    function handler() {
      console.log("inner")
    }

    btn.attachEvent("onclick", handler) // 绑定事件
    // btn.detachEvent("onclick", handler) // 移除事件
  </script>
</body>

事件冒泡的使用场景
原理:利用冒泡的原理,把事件添加到父元素上,委托它们父级代为执行事件

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>JS 事件委托</title>
  <style>
    *{
      margin: 0;
      padding: 0;
    }
    ul{
      list-style: none;
      width: 400px;
      border: 1px solid #000;
      margin: 100px auto;
    }
    li{
      width: 100%;
      height: 50px;
      border-bottom: 1px solid #000;
      box-sizing: border-box;
    }
    .selected{
      background-color: red;
    }
  </style>
</head>
<body>
  <ul>
    <li class="selected">1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
  </ul>

  <script>
    let uItems = document.querySelectorAll('li');
    let currentItem = uItems[0];
    for (let item of uItems){
      item.onclick = changeBg;
    }

    function changeBg() {
      currentItem.className = '';
      this.className = 'selected';
      currentItem = this;
    }
  </script>
</body>
</html>

这是一般做法,缺点是保存了多份onclick的函数

事件委托的做法:监听ul的点击,而不是直接监听li的点击,li将事件冒泡到它的父元素ul,再通过事件对象拿到当前被点击的元素

<script>
    let oUl = document.querySelector('ul');
    let oLi = document.querySelector('.selected');
    oUl.onclick = function (event) {
      event = event || window.event; // 兼容所有浏览器
      oLi.className = '';
      let item = event.target; // event.target为当前点击的li
      item.className = 'selected';
      oLi = item;
    }
  </script>

事件委托的好处:

  • 减少事件数量,提高性能,减少内存
  • 预测未来元素,新添加的元素仍然可以触发该事件
  • 避免内存外泄,在低版本的IE中,防止删除元素而没有移除事件而造成的内存溢出

事件委托的坏处:

  • 事件委托基于冒泡,不冒泡的事件不支持
  • 层级过多,冒泡过程中可能被中间层阻止
  • 如果把所有事件都用事件委托,可能会出现事件误判,即不该触发事件的被绑定了

项目地址:https://gitee.com/smallgrey/js

最近发表
标签列表