DOM事件避坑指南

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

1.JPG


DOM树 ~ 这个很关键



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

2.JPG

3.JPG


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


其树结构如下。


4.JPG


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


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

5.JPG


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级的事件



事件机制


6.JPG


三个阶段


  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



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


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


7.JPG


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

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

8.JPG


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,一起学习。

相关文章
|
7月前
|
XML 消息中间件 JavaScript
从0开始学习JavaScript--JavaScript DOM操作与事件处理
在前端开发中,DOM(文档对象模型)是一个至关重要的概念,它为JavaScript提供了一种与HTML和XML文档交互的方法。本文将深入探讨DOM的概念与作用,以及JavaScript与DOM之间的密切关系。
|
1月前
|
缓存 JavaScript 前端开发
JavaScript 与 DOM 交互的基础及进阶技巧,涵盖 DOM 获取、修改、创建、删除元素的方法,事件处理,性能优化及与其他前端技术的结合,助你构建动态交互的网页应用
本文深入讲解了 JavaScript 与 DOM 交互的基础及进阶技巧,涵盖 DOM 获取、修改、创建、删除元素的方法,事件处理,性能优化及与其他前端技术的结合,助你构建动态交互的网页应用。
49 5
|
3月前
|
JavaScript 前端开发
JavaScript HTML DOM 事件
JavaScript HTML DOM 事件
28 5
|
2月前
|
JavaScript 前端开发 API
深入理解jQuery:高效DOM操作与事件处理
【10月更文挑战第11天】深入理解jQuery:高效DOM操作与事件处理
29 0
|
3月前
|
前端开发 JavaScript
CSS中禁用DOM事件
CSS中禁用DOM事件
|
5月前
|
移动开发 JavaScript 前端开发
VUE实现一个列表清单【props 父子组件通信、slot插槽的使用、全局自定义指令的封装、$nextTick解决异步DOM更新、巧用v-model简化父子组件之间的通信、触发事件的事件源event】
VUE实现一个列表清单【props 父子组件通信、slot插槽的使用、全局自定义指令的封装、$nextTick解决异步DOM更新、巧用v-model简化父子组件之间的通信、触发事件的事件源event】
49 0
|
7月前
|
JavaScript
js_操作dom(委托和冒泡事件)
js_操作dom(委托和冒泡事件)
44 0
|
XML JavaScript 前端开发
|
JavaScript
JS(Dom事件操作)第十九
JS(Dom事件操作)第十九
190 0
|
7月前
|
存储 JavaScript 前端开发
JavaScript:DOM事件
JavaScript:DOM事件
58 0