【DOM事件】DOM事件及事件委托

801 阅读2分钟

目录

  • 点击事件?
  • 事件的捕获和冒泡
  • addEventListener
  • 代码图解
  • target VS currentTarget
  • 事件委托
  • 自定义事件

三、点击事件

以下引出一个讨论的问题

  <div class='爷爷'>
      <div class='爸爸'>
          <div class='儿子'>
          文字
          </div>
      </div>
  </div>
  <script>
      /* 均添加点击事件 */
      爷爷.addEventListener('click',fn);
      爸爸.addEventListener('click',fn);
      儿子.addEventListener('click',fn);
  </script>
  • 提问1:点击了谁,又算谁?点击了文字,算不算点击了儿子、算不算点击了爸爸、算不算点击了爷爷?
    • 答:都算!

  • 提问2:调用顺序? 最先调用fn爷、fn爸、fn儿中的那一个函数?
    • 答:都行!

四、事件的捕获与冒泡

  • 2002年,W3C发布标准

    文档名为:DOM Level 2 Events Specification

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

    首先 按 "爷爷 -> 爸爸 -> 儿子" 的顺序看看有没有函数监听。

    然后 按 "儿子 -> 爸爸 -> 爷爷" 的顺序看看有没有函数监听。

    有函数监听就调用,并提供事件信息。没有就跳过。

  • 事件捕获:从外向内找监听函数 -- 捕获不可以取消

  • 事件冒泡:从内向外找监听函数 -- 冒泡可以取消

  • 问题来了:那岂不是fn爷fn爸fn儿都要调用两次?非也!开发者自己选择把函数监听调用放在捕获阶段还是冒泡阶段


五、addEventlistener

  • 事件绑定API

      绑定事件对象.addEventListener(事件类型,回调函数,布尔值) 
    
    1. 如果不传布尔值或为falsy值 -- fn就会走冒泡阶段,
    2. 如果布尔值为true -- fn 就会走捕获阶段
    1. 你可以选择将fn放在那一边。

  • 代码示例
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>冒泡demo</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>
     <script>
        /* 要演示捕获阶段,将addEcentListener添加上第三个参数true即可 */
    
        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
    
        level1.addEventListener('click', (e)=>{
        const t = e.currentTarget
        setTimeout(()=>{  
            t.classList.remove('x')
        },n*1000)
        n+=1
        })
    
        level2.addEventListener('click', (e)=>{
        const t = e.currentTarget
        setTimeout(()=>{  
            t.classList.remove('x')
        },n*1000)
        n+=1
        })
    
        level3.addEventListener('click', (e)=>{
        const t = e.currentTarget
        setTimeout(()=>{  
            t.classList.remove('x')
        },n*1000)
        n+=1
        })
    
        level4.addEventListener('click', (e)=>{
        const t = e.currentTarget
        setTimeout(()=>{  
            t.classList.remove('x')
        },n*1000)
        n+=1
        })
    
        level5.addEventListener('click', (e)=>{
        const t = e.currentTarget
        setTimeout(()=>{  
            t.classList.remove('x')
        },n*1000)
        n+=1
        })
    
        level6.addEventListener('click', (e)=>{
        const t = e.currentTarget
        setTimeout(()=>{  
            t.classList.remove('x')
        },n*1000)
        n+=1
        })
    
        level7.addEventListener('click', (e)=>{
        const t = e.currentTarget
        setTimeout(()=>{  
            t.classList.remove('x')
        },n*1000)
        n+=1
        })
     </script>
    </body>
    </html>
    

  • W3C事件模型

    1、先捕获,在冒泡

    2、注意 e对象被传给所有的监听函数

    3、事件结束后,e 对象也就不存在了。

    4、事件列表


六、target VS currentTarger

  • 区别
    1. e.target -- 用户操作的元素
    2. e.currentTarget -- 程序员监听的元素
    3. this就是e.currentTarget
  • 举例
      <div>
      	<span>文字</span>
      </div>
    
      | 当用户点击文字后,
      | e.target就是span
      | e.currengtTarget就是div
    
  • 一个特例

    只有一个元素被监听(不考虑父子同时被监听),fn 分别在捕获和冒泡阶段监听click事件,用户点击的元素就是开发者监听的

    <div>这是被监听的div</div>
    
    <script>
        let div=document.querySelector('div');
        div.addEventListener('click',()=>{
            console.log(1);
        })	//冒泡阶段
    
        div.addEventListener('click',()=>{
            console.log(2);
        },true)	//捕获阶段
    </script>
    
  • 请问:输出 1 2 ,还是输出 2 1? 正确答案:谁先监听谁先执行。

七、事件委托

  • 情景一

    你要给100个按钮做监听事件,咋办?

  • 情景二

    你要监听目前不存在的元素的事件,咋办?

  • 正确方案:监听这些元素的祖先,等冒泡或事件触发时判断是不是我想要监听的元素即可。
  • 代码示例
    <!DOCTYPE>
    <html>
    <head>
        <meta charset='UTF-8'>
        <title>事件委托</title>
    <head>
    <body>
        <div class='max'>
            <button class='btn-1'>btn1</button>
            <button class='btn-2'>btn2</button>
            <button class='btn-3'>btn3</button>
                        .
                        . (省略96个button)
                        .
            <button class='btn-100'>btn100</button>
        </div>
        <script>
             /* 监听祖先元素 */
             let btn_max=document.querySelector('.max');
             btn_max.addEventListener('click',()=>{
                 const t = e.target;	//为了保存e.target对象,因为它是一个异步的结果,点击后e.target对象就会消失。
                 if(e.tagName.toLowerCase()==='button'){
                     console.log('button被点击了');
                     console.log('被点击的button是'+ t.className);	//这样就能知道点击了哪一个button
                 }
             })
        </script>
    </body>
    <html>
    

八、自定义事件

  <!DOCTYPE html>
  <html>
  <head>
  <meta charset="utf-8">
  <title>JS Bin</title>
  </head>
  <body>
      <div id=div1>
          <button id=button1>点击触发 frank 事件     
          </button>
      </div>
      <script>
          button1.addEventListener('click', ()=>{
              const event = new CustomEvent("frank", {"detail":{name:'frank', age: 18}})
              button1.dispatchEvent(event)
          })

          button1.addEventListener('frank', (e)=>{
              console.log('frank')
              console.log(e)
          })
      </script>
  </body>
  </html>

以上代码就创建了一个自定义事件,详情更多内容