美文网首页饥人谷技术博客
DOM 事件模型或 DOM 事件机制

DOM 事件模型或 DOM 事件机制

作者: 招投标秘籍 | 来源:发表于2021-05-23 10:50 被阅读0次

    1.什么叫事件

    通俗理解,事件是用户或者浏览器自己执行的某种动作,是文档或者浏览器发生的一些交互瞬间,比如点击(click)按钮等,这里的click就是事件的名称。JS与html之间的交互是通过事件实现的,DOM支持大量的事件。DOM 事件模型这个知识点,在面试的时候也是常考知识~

    2.事件模型

    一个事件发生后,会在子元素及父元素之间进行传播(propagation),这种传播分为三个阶段。
    (这种三阶段的传播模型,使得同一个事件会在多个节点上触发。)
    1.由外向内找监听函数就是事件捕获
    2.在目标节点触发事件
    3.由内而外找监听函数就是事件冒泡
    通俗一点来说就是一个事件被触发时,浏览器会自动从用户操作标签外的最上级标签逐渐向里检查是否有相同事件,如果有则触发,如果没有则继续向下检查知道用户操作的标签,这过程称为捕获,此时浏览器会继续由用户操作标签继续向是上级标签检查,如果有相同事件则触发,如果没有则继续向上检查直到最上级元素为止,此过程称为冒泡。(有监听函数就执行,并提供事件信息,没有就跳过)
    事件传播的最上层对象是window,上例的事件传播顺序,在捕获阶段依次为window、document、html、body、父节点、目标节点,在冒泡阶段依次为目标节点、父节点、body、html、document、window。
    DOM事件传播的三个阶段:捕获阶段,目标阶段,冒泡阶段

    3.点击事件

    <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中的那一个函数?
    答案:都行

    IE5认为先调用fnEr,网景认为先调用fnYe,最后遇到了W3C
    2002年,w3c发布标准
    文档名为DOM Level 2 Events Specification
    规定浏览器应该同时支持两种调用顺序
    首先按照grandfather->father->son
    然后按照son->father->grandfather
    
    术语:
    从外向内找监听函数,叫做事件捕捉
    从内向外找监听函数,叫做事件冒泡
    那岂不是fnYe/fnBa/fnEr都调用两次,非也!
    开发者可以自己决定把fnYe放在捕捉阶段还是放在冒泡阶段
    
    image.png

    3.1addEventListener事件绑定API

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

    如果bool不传或为falsy
    就让fn走冒泡,即当浏览器在冒泡阶段发现baba有fn监听函数,就会调用fn,并提供时间信息。
    如果bool为true
    就让fn走捕获,即当浏览器在捕获阶段发现baba有fn监听函数,就会调用fn,并且提供事件信息。
    下面用一个例子来说明一下

    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="utf-8">
      <title>JS Bin</title>
    </head>
    <body>
    <div class="level1 x">
      <div class="level2 x">
        <div class="level3 x">
          <div class="level4 x">
            <div class="level5 x">
              <div class="level6 x">
                <div class="level7 x">
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
    </body>
    </html>
    
    * {
      box-sizing: border-box;
    }
    div[class^=level] {
      border: 1px solid;
      border-radius: 50%;
      display: inline-flex;
    }
    .level1 {
      padding: 10px;
      background: purple;
    }
    .level2 {
      padding: 10px;
      background: blue;
    }
    .level3 {
      padding: 10px;
      background: cyan;
    }
    .level4 {
      padding: 10px;
      background: green;
    }
    .level5 {
      padding: 10px;
      background: yellow;
    }
    .level6 {
      padding: 10px;
      background: orange;
    }
    .level7 {
      width: 50px;
      height: 50px;
      border: 1px solid;
      background: red;
      border-radius: 50%;
    }
    .x{
      background: transparent;//把元素的变为透明
    }
    
    const level1 = document.querySelector('.level1')
    const level2 = document.querySelector('.level2')
    const level3 = document.querySelector('.level3')
    const level4 = document.querySelector('.level4')
    const level5 = document.querySelector('.level5')
    const level6 = document.querySelector('.level6')
    const level7 = document.querySelector('.level7')
    let n = 1
     const fm = (e)=>{
      const t = e.currentTarget
      setTimeout(()=>{  
        t.classList.remove('x')
      },n*1000)
      n+=1
    }
     const fa = (e)=>{
       const t =e.currentTarget
       setTimeout(()=>{
         t.classList.add('x')
       },n*1000)
       n+=1
     }
    level1.addEventListener('click',fm,true)
    level1.addEventListener('click',fa)
    level2.addEventListener('click',fm,true)
    level2.addEventListener('click',fa)
    level3.addEventListener('click',fm,true)
    level3.addEventListener('click',fa)
    level4.addEventListener('click',fm,true)
    level4.addEventListener('click',fa)
    level5.addEventListener('click',fm,true)
    level5.addEventListener('click',fa)
    level6.addEventListener('click',fm,true)
    level6.addEventListener('click',fa)
    level7.addEventListener('click',fm,true)
    level7.addEventListener('click',fa)
    

    完整代码链接:http://js.jirengu.com/tisujegazo/1/edit
    代码图示

    image.png

    4.target v.s. currentTarget的区别

    区别:

    e.target - 用户操作的元素
    e.currentTarget-程序员监听的元素
    this是e.currentTarget,我个人不推荐使用它

    举例:

    div>span{文字},用户点击文字
    e.target就是span
    e.currentTarget就是div
    一个特例
    背景:
    只有一个div被监听(不考虑父子同时被监听)
    fn分别再捕获阶段和冒泡阶段监听click事件
    用户点击的元素就是开发者监听的
    代码:

    div.addEventListenter('click',f1)
    div.addEventListenter('click',f2,true)
    

    请问,f1先执行还是f2先执行?
    如果把两个调换位置?
    总结:谁先监听谁先执行。

    level7.addEventListener('click',()=>{
          console.log(2)
    },true)//捕获
    level7.addEventListener('click',()=>{
          console.log(1)
    })//冒泡
    

    5.e.stopPropagation():取消冒泡

    e.stopPropagation()可打断冒泡,浏览器不再向上走
    一般用于封装某些独立组件
    注意:捕获不可以取消但是冒泡可以(有些事件也不能够取消冒泡)

    6.事件委托

    我委托一个元素帮我监听我本该监听的东西,比如onclick
    场景1:
    要给100个按钮添加点击事件,咋办?
    答:监听这个100个按钮的祖先,等冒泡的时候判断target是不是这100个按钮中的一个

    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="utf-8">
      <title>JS Bin</title>
    </head>
    <body>
    <div id="div1">
      <span>span1</span>
       <button>click 1</button>
       <button>click 2</button>
       <button>click 3</button>
       <button>click 4</button>
       <button>click 5</button>
       <button>click 6</button>
       <button>click 7</button>
       <button>click 8</button>
       <button>click 9</button>
       <button>click 10</button>
       <button>click 11</button>
       <button>click 12</button>
       <button>click 13</button>
       <button>click 14</button>
       <button>click 15</button>
       <button>click 16</button>
       <button>click 17</button>
       <button>click 18</button>
       <button>click 19</button>
       <button>click 20</button>
       <button>click 21</button>
       <button>click 22</button>
       <button>click 23</button>
       <button>click 24</button>
       <button>click 25</button>
       <button>click 26</button>
       <button>click 27</button>
       <button>click 28</button>
       <button>click 29</button>
       <button>click 30</button>
       <button>click 31</button>
       <button>click 32</button>
       <button>click 33</button>
       <button>click 34</button>
       <button>click 35</button>
       <button>click 36</button>
       <button>click 37</button>
       <button>click 38</button>
       <button>click 39</button>
       <button>click 40</button>
       <button>click 41</button>
       <button>click 42</button>
       <button>click 43</button>
       <button>click 44</button>
       <button>click 45</button>
       <button>click 46</button>
       <button>click 47</button>
       <button>click 48</button>
       <button>click 49</button>
       <button>click 50</button>
       <button>click 51</button>
       <button>click 52</button>
       <button>click 53</button>
       <button>click 54</button>
       <button>click 55</button>
       <button>click 56</button>
       <button>click 57</button>
       <button>click 58</button>
       <button>click 59</button>
       <button>click 60</button>
       <button>click 61</button>
       <button>click 62</button>
       <button>click 63</button>
       <button>click 64</button>
       <button>click 65</button>
       <button>click 66</button>
       <button>click 67</button>
       <button>click 68</button>
       <button>click 69</button>
       <button>click 70</button>
       <button>click 71</button>
       <button>click 72</button>
       <button>click 73</button>
       <button>click 74</button>
       <button>click 75</button>
       <button>click 76</button>
       <button>click 77</button>
       <button>click 78</button>
       <button>click 79</button>
       <button>click 80</button>
       <button>click 81</button>
       <button>click 82</button>
       <button>click 83</button>
       <button>click 84</button>
       <button>click 85</button>
       <button>click 86</button>
       <button>click 87</button>
       <button>click 88</button>
       <button>click 89</button>
       <button>click 90</button>
       <button>click 91</button>
       <button>click 92</button>
       <button>click 93</button>
       <button>click 94</button>
       <button>click 95</button>
       <button>click 96</button>
       <button>click 97</button>
       <button>click 98</button>
       <button>click 99</button>
       <button>click 100</button>
    </div>
    </body>
    </html>
    
    div1.addEventListener('click',(e)=>{
      const t = e.target
      if(t.tagName.toLowerCase()==='button'){
        console.log('button 被点击了')
    }
    })//toLowerCase小写
    
    image.png

    场景2:
    你要监听目前不存在的元素的点击事件?
    答:监听祖先,等点击的时候看看是不是监听的元素即可。
    优点:省监听数(内存),可以动态监听元素

    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="utf-8">
      <title>JS Bin</title>
    </head>
    <body>
      <div id=div1>   
      </div>
    </body>
    </html>
    
    setTimeout(()=>{
      const button = document.createElement('button')
      button.textContent='click 1'
      div1.appendChild(button)
    },1000)
    div1.addEventListener('click',(e)=>{
      const t = e.target
      if(t.tagName.toLowerCase()==='button'){
        console.log('button被click')
      }
    })
    

    6.1封装一个事件委托

    只要实行一个函数就可以实现事件委托
    要求:
    写出这样一个函数on('click','#testDiv','li',fn)
    当用户点击#testDiv里面的li元素时,调用fn函数
    要求用到事件委托
    答案1:判断target是否匹配'li'
    答案2:target/target的爸爸/target的爷爷

    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="utf-8">
      <title>JS Bin</title>
    </head>
    <body>
      <div id=div1>   
      </div>
    </body>
    </html>
    
    setTimeout(()=>{
      const button = document.createElement('button')
      button.textContent='click 1'
      div1.appendChild(button)
    },1000)
    
    on('click','#div1','button',()=>{//'#div'是选择器不是元素
      console.log('button 被点击啦')
    })
    function on(eventType,element,selector,fn){
      if(!(element instanceof Element)){
           element = document.querySelector(element)
         }
      element.addEventListener(eventType,(e)=>{
      const t= e.target//被点击的元素
      if(t.matches(selector)){//matches用来判断一个元素是否匹配一个选择器,selector是不是一个选择器
        fn(e)
       }
    })
    }
    

    本文为本人的原创文章,著作权归本人和饥人谷所有,转载务必注明来源.

    相关文章

      网友评论

        本文标题:DOM 事件模型或 DOM 事件机制

        本文链接:https://www.haomeiwen.com/subject/dwuojltx.html