JavaScript的事件详解

268 阅读4分钟

1. 事件处理方案

  • 事件监听方式一:在script中直接监听(很少使用)

  • 事件监听方式二:DOM属性,通过元素的on来监听事件

  • 事件监听方式三:通过EventTarget中的addEventListener来监听

    <body>
    
      <!-- 直接在html中编写JavaScript代码(了解) -->
      <button onclick="console.log('按钮1发生了点击~');">按钮1</button>
    
      <button class="btn2">按钮2</button>
      <button class="btn3">按钮3</button>
    
      <script>
    
        // 1.获取元素对象
        var btn2El = document.querySelector(".btn2")
        var btn3El = document.querySelector(".btn3")
    
        // 2.onclick属性
        // function handleClick01() {
        //   console.log("按钮2发生了点击~")
        // }
        // function handleClick02() {
        //   console.log("按钮2的第二个处理函数")
        // }
        // 这种方式无法执行多个函数
        // btn2El.onclick = handleClick01
        // btn2El.onclick = handleClick02
    
        // 3.addEventListener(推荐)
        btn3El.addEventListener("click", function() {
          console.log("第一个btn3的事件监听~")
        })
        btn3El.addEventListener("click", function() {
          console.log("第二个btn3的事件监听~")
        })
        btn3El.addEventListener("click", function() {
          console.log("第三个btn3的事件监听~")
        })
    
      </script>
    
    </body>
    

2. 事件捕获冒泡

  • 事件流:在浏览器上对着一个元素点击时,点击的不仅仅是这个元素本身,因为 HTML 元素是存在父子元素叠加层级的,比如一个span元素是放在div元素上的,div元素是放在body元素上的,body元素是放在html元素上的。

  • 不同传递

    • 默认情况下事件是从最内层的span向外依次传递的顺序,这个顺序称之为事件冒泡(Event Bubble)
    • 另外一种监听事件流的方式就是从外层到内层(body -> span),这种称之为事件捕获(Event Capture)
  • addEventListener("click", fn, true):第三个参数为 true 能够监听事件捕获
  • 如果都监听,会按照如下顺序来执行:

    • 捕获阶段(Capturing phase):事件(从 Window)向下走近元素
    • 目标阶段(Target phase):事件到达目标元素
    • 冒泡阶段(Bubbling phase):事件从元素上开始冒泡

    image.png

3. 事件对象event

  • 当一个事件发生时,会有和这个事件相关的很多信息:比如事件的类型,点击的是哪一个元素,点击的位置等等相关的信息,这些信息会被封装到一个Event对象中,这个对象由浏览器创建,称之为event对象

  • event对象会在传入的事件处理(event handler)函数回调时,被系统传入,可以在回调函数中拿到这个event对象

  • 常见的属性:

    • type:事件的类型
    • target:当前事件发生的元素
    • currentTarget:当前处理事件的元素
    • eventPhase:事件所处的阶段
    • offsetX、offsetY:事件发生在元素内的位置
    • clientX、clientY:事件发生在客户端内的位置
    • pageX、pageY:事件发生在客户端相对于document的位置
    • screenX、screenY:事件发生相对于屏幕的位置
  • target和currentTarget的区别:

    • 对于以下代码,如果点击的是div元素,两者完全一致,
    • 如果点击的是span元素,target指的是span元素,而currentTarget指的是div元素(即当前绑定事件的元素,由于span冒泡监听到的)
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <style>
        .box {
          display: flex;
          width: 200px;
          height: 200px;
          background-color: orange;
        }
    
        span {
          width: 100px;
          height: 100px;
          background-color: #f00;
        }
      </style>
    </head>
    <body>
    
      <div class="box">
        <span class="btn">
          <button>按钮</button>
        </span>
      </div>
    
      <br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
      <br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
      <br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
    
      <script>
    
        var divEl = document.querySelector("div")
        var btnEl = document.querySelector(".btn")
        
        divEl.onclick = function(event) {
          // 1.偶尔会使用
          console.log("事件类型:", event.type)
          console.log("事件阶段:", event.eventPhase)
    
          // 2.使用较少
          console.log("事件元素中位置", event.offsetX, event.offsetY)
          console.log("事件客户端中位置", event.clientX, event.clientY)
          console.log("事件页面中位置", event.pageX, event.pageY)
          console.log("事件在屏幕中位置", event.screenX, event.screenY)
    
          // 3.target/currentTarget
          console.log(event.target)
          console.log(event.currentTarget)
          console.log(event.currentTarget === event.target)
        }
    
      </script>
    
    </body>
    </html>
    
  • 两个方法

    • preventDefault:阻止默认行为
    • stopPropagation:阻止事件传递
      <!DOCTYPE html>
      <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
          .box {
            display: flex;
            width: 200px;
            height: 200px;
            background-color: orange;
          }
      
          .box span {
            width: 100px;
            height: 100px;
            background-color: #f00;
          }
        </style>
      </head>
      <body>
      
        <a href="http://www.baidu.com">百度一下</a>
      
        <div class="box">
          <span>
            <button>按钮</button>
          </span>
        </div>
      
        <script>
          // 1.阻止默认行为
          // var aEl = document.querySelector("a")
          // aEl.onclick = function(event) {
          //   console.log("a元素发生了点击~")
          //   event.preventDefault()
          // }
      
          // 2.阻止事件进一步传递
          var btnEl = document.querySelector("button")
          var spanEl = document.querySelector("span")
          var divEl = document.querySelector("div")
      
          divEl.addEventListener("click", function(event) {
            console.log("div的事件捕获监听~")
            // event.stopPropagation()
          }, true)
          spanEl.addEventListener("click", function() {
            console.log("span的事件捕获监听~")
          }, true)
          btnEl.addEventListener("click", function(event) {
            console.log("button的事件捕获监听~")
            // event.stopPropagation()
          }, true)
      
          divEl.addEventListener("click", function() {
            console.log("div的事件冒泡监听~")
          })
          spanEl.addEventListener("click", function(event) {
            console.log("span的事件冒泡监听~")
            event.stopPropagation()
          })
          btnEl.addEventListener("click", function() {
            console.log("button的事件冒泡监听~")
          })
      
        </script>
      
      </body>
      </html>
      

4. 事件函数中的this

  • 处理的元素

    <body>
    
      <div>
        <button>按钮</button>
      </div>
    
      <script>
    
        var btnEl = document.querySelector("button")
        var divEl = document.querySelector("div")
    
        divEl.onclick = function(event) {
          console.log(this)
          console.log(event.currentTarget)
          console.log(divEl)
          console.log(this === divEl)
        }
        // divEl.addEventListener("click", function() {
        //   console.log(this)
        // })
    
      </script>
    </body>
    

5. EventTarget的使用

  • 所有的节点、元素都继承自EventTarget

    • Window也继承自EventTarget

    image.png

  • EventTarget是一个DOM接口,主要用于添加、删除、派发Event事件

  • EventTarget常见的方法:

    • addEventListener:注册某个事件类型以及事件处理函数
    • removeEventListener:移除某个事件类型以及事件处理函数
    • dispatchEvent:派发某个事件类型到EventTarget上
    <body>
    
      <button>按钮</button>
    
      <script>
    
        var btnEl = document.querySelector("button")
    
        // 1.将监听函数移除的过程
        // var foo = function() {
        //   console.log("监听到按钮的点击")
        // }
        // btnEl.addEventListener("click", foo)
    
        // // 需求: 过5s钟后, 将这个事件监听移除掉
        // setTimeout(function() {
        //   btnEl.removeEventListener("click", foo)
        // }, 5000)
    
        // 这种做法是无法移除的
        btnEl.addEventListener("click", function() {
          console.log("btn监听的处理函数~")
        })
    
        setTimeout(function() {
          btnEl.removeEventListener("click", function() {})
        }, 5000)
       
        
        // eventtarget就可以实现类似于事件总线的效果
        window.addEventListener("abc", function() {
          console.log("监听到abc的呼唤~")
        })
    
        setTimeout(function() {
          window.dispatchEvent(new Event("abc"))
        }, 5000)
        
      </script>
    </body>
    

6. 事件委托(delegation)

  • 原理:利用事件的冒泡机制,以及事件对象中可以准确获知触发事件的元素机制(e.target),将子元素事件委托给父元素处理的现象
  • 案例一:ul 中 li 点击时active,其他的 li 取消active

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <style>
        .active {
          color: red;
          font-size: 20px;
          background-color: orange;
        }
      </style>
    </head>
    <body>
    
      <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
        <li>6</li>
        <li>7</li>
        <li>8</li>
        <li>9</li>
        <li>10</li>
      </ul>
    
      <script>
    
        // 1.每一个li都监听自己的点击, 并且有自己的处理函数(自己的函数)
        // var liEls = document.querySelectorAll("li")
        // for (var liEl of liEls) {
        //   // 监听点击
        //   liEl.onclick = function(event) {
        //     event.currentTarget.classList.add("active")
        //   }
        // }
    
        // 2.统一在ul中监听
        // var ulEl = document.querySelector("ul")
        // ulEl.onclick = function(event) {
        //   console.log("点击了某一个li", event.target)
        //   event.target.classList.add("active")
        // }
    
        // 3.点击li变成active, 其他的取消active
        var ulEl = document.querySelector("ul")
        var activeLiEl = null
        ulEl.onclick = function(event) {
          // 1.将之前的active移除掉
          // for (var i = 0; i < ulEl.children.length; i++) {
          //   var liEl = ulEl.children[i]
          //   if (liEl.classList.contains("active")) {
          //     liEl.classList.remove("active")
          //   }
          // }
    
          // 1.找到active的li, 移除掉active
          // var activeLiEl = ulEl.querySelector(".active")
          // if (activeLiEl) {
          //   activeLiEl.classList.remove("active")
          // }
    
          // 1.变量记录的方式
          // edge case
          if (activeLiEl && event.target !== ulEl) {
            activeLiEl.classList.remove("active")
          }
    
          // 2.给点击的元素添加active
          if (event.target !== ulEl) {
              event.target.classList.add("active")
          }
    
          // 3.记录最新的active对应的li
          activeLiEl = event.target
        }
    
      </script>
    </body>
    </html>
    
  • 案例二:多个按钮的区分

    • data-*
      <body>
      
        <div class="box">
          <button data-action="search">搜索~</button>
          <button data-action="new">新建~</button>
          <button data-action="remove">移除~</button>
          <button>1111</button>
        </div>
      
        <script>
      
          var boxEl = document.querySelector(".box")
          boxEl.onclick = function(event) {
            var btnEl = event.target
            var action = btnEl.dataset.action
            switch (action) {
              case "remove":
                console.log("点击了移除按钮")
                break
              case "new":
                console.log("点击了新建按钮")
                break
              case "search":
                console.log("点击了搜索按钮")
                break
              default:
                console.log("点击了其他")
            }
          }
        </script>
      </body>
      

7. 鼠标事件

7.1 常见的事件

image.png

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .box {
      width: 200px;
      height: 200px;
      background-color: orange;
    }
  </style>
</head>
<body>

  <div class="box"></div>
  
  <script>
    // 鼠标事件
    var boxEl = document.querySelector(".box")

    boxEl.onclick = function() {
      console.log("click")
    }

    boxEl.oncontextmenu = function(event) {
      console.log("点击了右键")
      // 阻止默认行为
      event.preventDefault()
    }

    // 变量记录鼠标是否是点下去的
    var isDown = false
    boxEl.onmousedown = function() {
      console.log("鼠标按下去")
      isDown = true
    }

    boxEl.onmouseup = function() {
      console.log("鼠标抬起来")
      isDown = false
    }

    boxEl.onmousemove = function() {
      if (isDown) {
        console.log("鼠标在div上面移动")
      }
    }

  </script>
</body>
</html>

7.2 mouseenter和mouseover的区别

  • mouseenter和mouseleave
    • 不冒泡
    • 进入子元素的时候, 不会有任何反应
  • mouseover和mouseout
    • 会冒泡
    • 进入子元素
      • 先离开out父元素
      • 进入子元素over
      • 并且会冒泡给父元素 over
      <!DOCTYPE html>
      <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
          .box {
            display: flex;
            justify-content: center;
            align-items: center;
            width: 200px;
            height: 200px;
            background-color: orange;
          }
      
          .box span {
            width: 100px;
            height: 100px;
            background-color: red;
          }
        </style>
      </head>
      <body>
      
        <div class="box">
          <span></span>
        </div>
      
        <script>
          var boxEl = document.querySelector(".box")
          var spanEl = document.querySelector("span")
      
          // 1.第一组
          boxEl.onmouseenter = function() {
            console.log("box onmouseenter")
          }
          boxEl.onmouseleave = function() {
            console.log("box onmouseleave")
          }
      
          spanEl.onmouseenter = function() {
            console.log("span onmouseenter")
          }
          spanEl.onmouseleave = function() {
            console.log("span onmouseleave")
          }
      
          // 第二组
          // boxEl.onmouseover = function() {
          //   console.log("box onmouseover")
          // }
          // boxEl.onmouseout = function() {
          //   console.log("box onmouseout")
          // }
      
        </script>
      
      </body>
      </html>
      

8. 键盘事件

  • 常见的键盘事件

    image.png

  • 事件的执行顺序是 onkeydown、onkeypress、onkeyup

    • down事件先发生
    • press发生在文本被输入
    • up发生在文本输入完成(抬起、松开)
  • 通过key和code来区分按下的键:

    • code:“按键代码”("KeyA","ArrowLeft" 等),特定于键盘上按键的物理位置。
    • key:字符 ("A","a" 等,大小写区分),对于非字符(non-character)的按键,通常具有与 code 相同的值。)
    <body>
    
      <input type="text">
      <button>搜索</button>
    
      <script>
    
        var inputEl = document.querySelector("input")
        var btnEl = document.querySelector("button")
    
        // inputEl.onkeydown = function() {
        //   console.log("onkeydown")
        // }
        // inputEl.onkeypress = function() {
        //   console.log("onkeypress")
        // }
        // inputEl.onkeyup = function(event) {
        //   console.log(event.key, event.code)
        // }
    
        // 1.搜索功能
        btnEl.onclick = function() {
          console.log("进行搜索功能", inputEl.value)
        }
    
        inputEl.onkeyup = function(event) {
          if (event.code === "Enter") {
            console.log("进行搜索功能", inputEl.value)
          }
        }
    
        // 2.按下s的时候, 搜索自动获取焦点
        document.onkeyup = function(event) {
          if (event.code === "KeyS") {
            inputEl.focus()
          }
        }
    
      </script>
    </body>
    
  • 注意:keyCode 已废弃

    image.png

9. 表单事件

image.png

<body>
  
  <form action="/abc">
    <input type="text">
    <textarea name="" id="" cols="30" rows="10"></textarea>

    <button type="reset">重置</button>
    <button type="submit">提交</button>
  </form>

  <script>
    var inputEl = document.querySelector("input")

    // 1.获取焦点和失去焦点
    // inputEl.onfocus = function() {
    //   console.log("input获取到了焦点")
    // }
    // inputEl.onblur = function() {
    //   console.log("input失去到了焦点")
    // }

    // 2.内容发生改变/输入内容
    // 输入的过程: input
    // 内容确定发生改变(离开): change
    // inputEl.oninput = function() {
    //   console.log("input事件正在输入内容", inputEl.value)
    // }
    // inputEl.onchange = function() {
    //   console.log("change事件内容发生改变", inputEl.value)
    // }

    // 3.监听重置和提交
    var formEl = document.querySelector("form")
    formEl.onreset = function(event) {
      console.log("发生了重置事件")
      event.preventDefault()
    }

    formEl.onsubmit = function(event) {
      console.log("发生了提交事件")
      // axios库提交
      event.preventDefault()
    }

  </script>
</body>

10. 文档加载

  • DOMContentLoaded:浏览器已完全加载 HTML,并构建了 DOM 树,但像 <img> 和样式表之类的外部资源可能尚未加载完成

  • load:浏览器不仅加载完成了 HTML,还加载完成了所有外部资源:图片,样式等

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <style>
        .box {
          width: 200px;
          height: 200px;
        }
      </style>
    </head>
    <body>
    
      <script>
    
        // 注册事件监听
        window.addEventListener("DOMContentLoaded", function() {
          // 1.这里可以操作box, box已经加载完毕
          // var boxEl = document.querySelector(".box")
          // boxEl.style.backgroundColor = "orange"
          // console.log("HTML内容加载完毕")
    
          // 2.获取img对应的图片的宽度和高度
          var imgEl = document.querySelector("img")
          console.log("图片的宽度和高度:", imgEl.offsetWidth, imgEl.offsetHeight) // 0 0
        })
    
        window.onload = function() {
          console.log("文档中所有资源都加载完毕")
          // var imgEl = document.querySelector("img")
          // console.log("图片的宽度和高度:", imgEl.offsetWidth, imgEl.offsetHeight) // 450 281
        }
    
        // 屏幕大小发生变化
        window.onresize = function() {
          console.log("创建大小发生改变时")
        }
    
      </script>
    
      <div class="box">
        <p>哈哈哈啊</p>
      </div>
      <a href="#">百度一下</a>
      <img src="../images/kobe.jpg" alt="">
    
    </body>
    </html>
    
  • 其他事件学习可参考MDN官网