DOM事件避坑指南

·  阅读 914
DOM事件避坑指南

DOM树 ~ 这个很关键

<div>Hello world!</div>
复制代码

树的根节点是document, 不是window。 上图其实还少了一个节点,虽然作用不大,了解了解。

其树结构如下。

我们常用的document.head和document.body是对开发人员比较重要的两个节点快捷访问。

document.body === document.childNodes[1].childNodes[2]  // true
document.head === document.childNodes[1].childNodes[0]  // true
复制代码

window 和 document的关系

  1. BOM (Browser Object Model): 浏览器对象模,没有相关标准,一些和网页无关的浏览器功能。如:window、location、navigator、screen、history等对象

  2. DOM (Document Object Model): 文档对象模型, W3C 的标准, HTML和XML文档的编程接口。

window 属于 BOM, document 是DOM中的核心对象。但是window引用着document,仅此而已。

DOM0级的事件

<button onclick="console.log('被点击了')">按钮</button>
复制代码

优点

  1. 效率高

没有捕获冒泡等概念。

  1. 节点上onclick属性被Node.cloneNode克隆,通过JS赋值的onclick不可以

相对而言,DOM2级别的事件并不能复制。

  1. 移除事件非常简单

直接设置 onclick = null

  1. 兼容性好

复制的例子:

可以看到按钮2, 通过JS的对象模型赋值的onclick事件不可以被复制。 其实也很好理解,在节点上的属性值是作为字符串被复制的。

    <div id="sourceZone">
      <div>原始区域:</div>

      <div id="sourceButtons">
        <div>
          <button class="btn1" onclick="console.log('按钮1被点击了')">
            按钮1(代码)
          </button>
        </div>
        <div>
          <button class="btn2">按钮2(函数)</button>
        </div>
      </div>
    </div>

    <div id="copyZone">
      <div>复制区域:</div>
    </div>

    <script>

      document.querySelector(".btn2").onclick = onClick2;

      copyZone.append(sourceButtons.cloneNode(true));

      function onClick2() {
        console.log("按钮2被点击了");
      }

      (function () {
        function onClick4() {
          console.log("按钮4被点击了");
        }
      })();
    </script>
复制代码

注意事项

  1. this是当前的节点
  2. 如果调用函数,会在全局作用域查找
  3. 唯一性,只能定义一个的事件回调函数

DOM2级的事件

事件机制

三个阶段

  1. 捕获阶段,从外向内
  2. 目标阶段,转折点
  3. 冒泡阶段,从内向外

事件注册

这三个阶段在代码层面怎么体现,答案就是注册事件监听函数addEventListener的参数。

target.addEventListener(type, listener, useCapture);
target.addEventListener(type, listener, options);
复制代码

我们先看第一个语法

useCapture: true,捕获阶段传播到目标的时候触发,反之冒泡阶段传到目标的时候触发。默认值flase, 即冒泡时。

当然,你也可以捕获和冒泡时都触发。

看个例子:

输出顺序: 捕获:document => 捕获:btn =>冒泡:btn=>冒泡:document

    <button id="btn">按钮</button>

    <script>
      btn.addEventListener("click", function (ev) {
        console.log("冒泡:btn");
      });

      btn.addEventListener(
        "click",
        function (ev) {
          console.log("捕获:btn");
        },
        true
      );

      
      document.addEventListener("click", function(){
        console.log("冒泡:document");
      })

      document.addEventListener("click", function(){
        console.log("捕获:document");
      }, true)
复制代码

强大的options

target.addEventListener(type, listener, options);
复制代码

options:

  • capture: 是否是捕获阶段处理。
  • once: 是否是只执行一次。
  • passive:
  • signal:

once

是否只执行一次。 这个参数非常有用,估计知道的人不多。 如果想让某个事件只执行一次,非你莫属。

最典型的应用就是 视频播放,现代浏览器可能需要用户参与后,视频才可以有声播放。

    <button id="btn">按钮</button>
    <script>
        btn.addEventListener("click", function () {
            console.log("按钮被点击了")
        }, {
            once: true,
        })
    </script>
复制代码

passive

某些触摸事件(以及其他)的事件监听器在尝试处理滚动时, 可能阻止浏览器的主线程,从而导致滚动处理期间性能可能大大降低。

某些浏览器(特别是Chrome和Firefox)已将文档级节点 WindowDocumentDocument.bodytouchstart (en-US)touchmove (en-US)事件的passive选项的默认值更改为true。

var elem = document.getElementById('elem');
elem.addEventListener('touchmove', function listener() { /* do something */ }, 
 { passive: true });
复制代码

signal

这个signal是AbortController的一部分,其主要作用是用来终止请求。

而在此处的作用,效果等同于移除监听器。

有趣的 useCapture

如果这个参数相同并且事件回调函数相同,事件不会被添加。

     function onClick(){
            console.log("按钮被点击了");
        }
        // capture选项都是false, 只有一个添加成功
        btn.addEventListener("click", onClick);
        btn.addEventListener("click", onClick);

        // capture选项都是true, 只有一个添加成功
        btn.addEventListener("click", onClick, {
            capture: true,
        });
        btn.addEventListener("click", onClick, {
            capture: true,
            once: true,
        });
复制代码

阻止默认行为 event.preventDefault

  1. 阻止默认的行为,比如有href属性的a标签不会跳转,checkbox的选中不会生效等。
  2. 事件依旧还会继续传播
    <div>
        <a id="linkMK" target="_blank" href="https://www.imooc.com/">跳转到慕课</a>
    </div>
    <div>
        <input id="ckBox" type="checkbox"><label for="ckBox">我统一</label>
    </div>

    <script>
        linkMK.addEventListener("click", function(ev){
            ev.preventDefault();
        })

        ckBox.addEventListener("click", function(ev){
            ev.preventDefault();
        })
    </script>
复制代码

停止事件传播 stopPropagation

阻止捕获和冒泡阶段中当前事件的进一步传播。 有些说法是停止冒泡,个人觉得不太精准哈。

一旦调用,后续的阶段的监听函数均不会响应。

此外还有一个名字很近似的的 stopImmediatePropagation


阻止监听同一事件的其他事件监听器被调用,如果多个事件监听器被附加到相同元素的相同事件类型上,当此事件触发时,它们会按其被添加的顺序被调用。如果在其中一个事件监听器中执行 stopImmediatePropagation() ,那么剩下的事件监听器都不会被调用。


target 和 currentTarget

  • target: 触发事件的元素。 谁触发。
  • currentTarget: 事件绑定的元素。 谁添加的事件监听函数。
   <button id="btn">按钮</button>

    <script>
      btn.addEventListener("click", function (ev) {
      });

      document.addEventListener("click", function(ev){
        console.log("ev.target:", ev.target,)
        console.log("ev.currentTarget:", ev.currentTarget)
      })
复制代码

事件委托

利用事件传播的机制,利用外层节点处理事件的思路。

优点:

  1. 减少内存消耗
  2. "动态性"更好

因为监听事件是在上层注册,如上增加了节点,我们不需要单独再去添加事件监听。

两点想说:

  1. 其性能相对于直接添加触发事件的节点,没有得到提升。收益是节约了内存。 也算时间换空间。
  2. 如果判断事件该不该响应,一般是判断标签,class, 或者属性。
    <ul id="ulList">
        <li>
            <div>白菜</div>
            <a class="btn-buy" data-id="1">购买</a>
        </li>
        <li>
            <div>萝卜</div>
            <a class="btn-buy" data-id="2">购买</a>
        </li>
    </ul>

    <script>
        ulList.addEventListener("click", function (ev) {
            // console.log("ev", ev)
            // 识别节点
            if (ev.target.classList.contains("btn-buy")) {
                console.log("商品id:", ev.target.dataset.id)
            }
        })
    </script>
复制代码

DOM3级事件

DOM3 Events在DOM2 Events基础上重新定义了事件,并增加了新的事件类型

  1. 用户界面事件(UIEvent):涉及与BOM交互的通用浏览器事件。 比如:load、scroll
  2. 焦点事件(FocusEvent):在元素获得和失去焦点时触发。比如focus, blur
  3. 鼠标事件(MouseEvent):使用鼠标在页面上执行某些操作时触发。
  4. 滚轮事件(WheelEvent):使用鼠标滚轮(或类似设备)时触发。比如:mousewheel
  5. 输入事件(InputEvent):向文档中输入文本时触发。
  6. 键盘事件(KeyboardEvent):使用键盘在页面上执行某些操作时触发。比如如:keydown、keypress
  7. 合成事件(CompositionEvent):在使用某种IME(Input Method Editor,输入法编辑器)输入字符时触发。

注意事项和建议

  1. DOM0级事件一定程度上可以复制
  2. DOM2级别事件不可以复制
  3. 合理利用选项once
  4. 合理利用选型passive提升性能
  5. capture选项相同和并且事件回调函数相同,事件不会被添加
  6. 因为都是继承于EventTarget,任何一个节点都是事件中心
  7. 合理利用事件代理

写在最后

不忘初衷,有所得,而不为所累,如果你觉得不错,你的一赞一评就是我前行的最大动力。

技术交流群请到 这里来。 或者添加我的微信 dirge-cloud,一起学习。

分类:
前端
收藏成功!
已添加到「」, 点击更改