阅读 104

DOM事件和事件委托(dom事件流是什么意思)

DOM事件

首先先看下代码:

<div class="grandfather">
  <div class="father">
    <div class="son"></div>
    word
  </div>
</div>
即.grandfather>.father>.son
给三个div分别添加事件的监听fnYe/fnBa/fnEr复制代码

提问1:点击了谁?
点击文字,算不算点击儿子?
点击文字,算不算点击爸爸?
点击文字,算不算点击爷爷?
答案:都算

提问2:调用循序
点击文字,最先调用fnYe/fnBa/fnEr中的那一个函数?
答案:都行

2002年,w3c发布标准 文档名为DOM Level 2 Events Specification

规定浏览器应该同时支持两种调用顺序

首先按照grandfather->father->son

然后按照son->father->grandfather

外向内找监听函数,叫做事件捕捉

内向外找监听函数,叫做事件冒泡

开发者可以自己决定把fnYe放在捕捉阶段还是放在冒泡阶段

图示:

25394908-d91eee5c0a1f1abb.webp

addEventListener

  • 事件绑定API

IE5*:baba.attachEvent('onclick',fn)//冒泡
网景:baba.addEventListener('click',fn)//捕获
W3C:baba.addEventListener('click',fn,bool)复制代码

W3C: 如果bool不传或为falsy,就让fn走冒泡,即当浏览器在冒泡阶段发现baba有fn监听函数,就会调用fn,并提供事件信息。

如果bool为true就让fn走捕获,即当浏览器在捕获阶段发现baba有fn监听函数,就会调用fn,并且提供事件信息。

如下图:

image.png

target和currentTarget的区别

  • 区别

    e.target- 用户操作的元素
    e.currentTarget-程序员监听的元素复制代码
  • 举例

    <div>
        <span>文字</span>
    </div>复制代码
    • 当用户点击文字时

    • e.target是span

    • e.currentTarget是div

# e.stopPropagation():取消冒泡

e.stopPropagation()可打断冒泡,浏览器不再向上走,一般用于封装某些独立组件.

  • 所有冒泡皆可取消,默认动作有的可以取消有的不能取消

    • 如何查看事件默认动作

    • 在MDN中搜索事件时 Cancelable为NO则无法默认动作

    • 如下图:

image.png

事件委托

  • 定义

    • JS里的事件委托:就是当事件触发时,把要做的事委托给父元素来处理。

    • 再通俗点:就是自己的事不想干,叫它爸爸,甚至爷爷、甚至祖先来干。

  • 作用

    • 作用1:节约内存

    • 作用2:能为之后新增的DOM元素依然添加事件

  • 例子

    <ul>
        <li>隔壁老王1</li>
        <li>隔壁老王2</li>
        <li>隔壁老王3</li>
        <li>隔壁老王4</li>
        <li>隔壁老王5</li>
    </ul>
    <script>
        //找到所有li标签
        let liList = document.getElementsByTagName('li')
        //遍历每个li,并给每个li加点击事件
        for(let i=0;i<liList.length;i++){
            liList[i].onclick = function(){
                console.log(this.innerHTML)
            }
        }
    </script>复制代码
    1. 代码如下

代码解析:

给5个li标签加了点击事件,当界面上点击li时,会打印它们各自li标签显示的内容。

出现的问题:

此时5个li,看起来每个li的点击事件触发时调用的都是同一个函数,即:

function(){

    console.log(this.innerHTML);             

};复制代码

但其实并不是这样。每个li绑定的都是一个全新的函数,只不过每个函数的样子都一毛一样。

至此,我们可以得到结论,如果有5个li,那么就有5个函数会被创建在内存中占据空间,那如果有100个li呢?就会有100个长相一毛一样的函数在内存中常驻,对内存的开销是巨大的!

  • 使用事件委托来写

    let ul = document.getElementsByTagName('ul')[0];
        ul.onclick = function(e){
            e = e || window.event
            //e.target 表示被点击的那个li
            console.log(e.target.innerHTML) 
        }    
    复制代码

利用事件冒泡的原理,把事件加在父元素(ul)身上!

给所有li添加点击事件,只要加到它们的父元素ul身上的根本原因是利用了事件冒泡。也即:无论点击哪个li,都会自动触发ul的点击事件,然后在ul里通过e.target能获得真正被点击的那个li,继而拿到它的innerHTML

小结:如果给一堆元素加事件,并且事件触发时执行的代码都差不多时,就可以把事件加在父元素身上啦!这样可以更节省内存空间哦!

但如果ul中还有其他子元素,但是只想给li加事件,就需要加一个判断事件源.代码如下

    ul.onclick = function(e){
            e = e || window.event
            //e.target 表示被点击的那个li
            if(e.target.nodeName.toLowerCase() === 'li'){
                console.log(e.target.innerHTML) 
            }
        }复制代码
  • 例子2:新增元素没有绑定事件的问题

    <button id="add">添加一个li</button>
    <ul>
      <li>隔壁老王1</li>
      <li>隔壁老王2</li>
      <li>隔壁老王3</li>
      <li>隔壁老王4</li>
      <li>隔壁老王5</li>
    </ul>
    <script>
      //找到所有li标签
      let ul = document.getElementsByTagName("ul")[0];
      let liList = document.getElementsByTagName("li");
      console.log(liList);
      //遍历每个li, 并给每个li加点击事件;
      for (let i = 0; i < liList.length; i++) {
        liList[i].onclick = function () {
          console.log(this.innerHTML);
        };
      }
      document.getElementById("add").onclick = function () {
        let newLi = document.createElement("li");
        newLi.innerHTML = "我是新创建的li";
        ul.appendChild(newLi);
      };
      ```复制代码
    • 普通写法,代码如下

剖析代码:

1.页面刚开始加载时就默认存在的5个li是有点击事件的,但是点击按钮创建出来的li没有点击事件。
2.因为上面的JS代码是在页面刚加载时执行的,在当时因为不可能去点击按钮,所以能找到的li标签只有默认那5个,因此你打印liList,发现也只有5个。

 * 
复制代码

image.png
3.总结:因此遍历liList给每个元素加点击事件时,只能给这5个li加到点击事件。如果后面再有li标签,也不拥有点击事件。

  • 使用事件委托.代码如下

    <script>
     //找到所有li标签
     let ul = document.getElementsByTagName("ul")[0];
     let liList = document.getElementsByTagName("li");
     console.log(liList);
    
     ul.onclick = function (e) {
       e = e || window.event;
       //e.target 表示被点击的那个li
       if (e.target.nodeName.toLowerCase() === "li") {
         console.log(e.target.innerHTML);
       }
     };
     document.getElementById("add").onclick = function () {
       let newLi = document.createElement("li");
       newLi.innerHTML = "我是新创建的li";
       ul.appendChild(newLi);
     };复制代码

image.png

  • 总结

因为事件冒泡机制的存在,不管是原本有的li还是新创建的li,当事件触发时都会一级一级往上调用父元素的同名事件。因此,只要是点击的li标签,都会触发ul的点击事件,所以只要把事件加在ul身上就解决了不管新旧li标签都有点击事件的问题。


作者:huang99
链接:https://juejin.cn/post/7068955864186503204


文章分类
代码人生
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐