Skip to content

一. 认识事件处理


1. 认识事件(Event)

  • Web页面需要经常和用户之间进行交互,而交互的过程中我们可能想要捕捉这个交互的过程:

    • 比如用户点击了某个按钮、用户在输入框里面输入了某个文本、用户鼠标经过了某个位置
    • 浏览器需要搭建一条js代码和事件之间的桥梁
    • 当某个事件发生时,让js可以相应(执行某个函数),所以我们需要针对事件编写处理程序(handler
  • 如何进行事件监听呢?

    • 事件监听方式一:直接在html中编写js代码

      html
      <div id='box' onclick='alert("box点击")'></div>
    • 事件监听方式二:DOM属性,通过元素的onClick属性来监听事件

    js
    box.onclick = function() {
      alert('box点击')
    }
    • 事件监听方式三:通过EventTarget中的addEventListener方法来监听

      js
      box.addEventListener('click', function() {
        alert('box点击')
      })

2. 常见的事件列表

  • 鼠标事件:

    事件含义
    click当鼠标点击一个元素时(触摸屏设备会在点击时生成)
    mouseover / mouseout当鼠标指针移入/离开一个元素时
    mousedown / mouseup当在元素上按下/释放鼠标按钮时
    mousemove当鼠标在元素上移动时
  • 键盘事件:

    • keydownkeyup —— 当按下和松开一个按键时
  • 表单(form)元素事件:

    事件含义
    submit当表单提交时触发
    focus当访问者聚焦于一个元素时,例如聚焦于一个 <input>
    reset当表单被重置时触发
  • Document 事件:

    • DOMContentLoaded —— 当HTML的加载和处理均完成,DOM 被完全构建完成时,无需等待css、图片等资源加载完成
  • CSS 事件:

    • transitionend —— 当一个CSS动画完成时

二. 事件冒泡捕获


1. 认识事件流·

  • 事实上对于事件有一个概念叫做事件流,为什么会产生事件流呢?

    • 我们可以想到一个问题:当我们在浏览器上对着一个元素点击时,你点击的不仅仅是这个元素本身

    • 这是因为我们的HTML元素是存在父子元素叠加层级的

    • 比如一个span元素是放在div元素上的,div元素是放在body元素上的,body元素是放在html元素上的

      html
      <div class="box">
        <span class="word">哈哈</span>
      </div>
      
      <script>
      	var spanEl = document.querySelector('.word')
        var divEl = document.querySelector('.box')
        var bodyEl = document.body
        
        spanEl.addEventListener('click', function() {
          console.log('span被点击')
        })
        divEl.addEventListener('click', function() {
          console.log('div被点击')
        })
        bodyEl.addEventListener('click', function() {
          bodyEl.log('body被点击')
        })
      </script>

2. 事件冒泡和事件捕获

  • 我们会发现默认情况下事件是从最内层的span向外依次传递的顺序,这个顺序我们称之为事件冒泡(Event Bubble

  • 事实上,还有另外一种监听事件流的方式就是从外层到内层(body -> span),这种称之为事件捕获(Event Capture

  • 为什么会产生两种不同的处理流呢?

    • 这是因为早期浏览器开发时,不管是IE还是Netscape公司都发现了这个问题

    • 但是他们采用了完全相反的事件流来对事件进行了传递

    • IE采用了事件冒泡的方式,Netscape采用了事件捕获的方式

      image-20220523222240779

3. 事件捕获和冒泡的过程

  • 如果我们都监听,那么会按照如下顺序来执行:

    image-20220523222652312
    1. 捕获阶段Capturing phase):事件对象从Window传播到目标元素的过程
    2. 目标阶段Target phase):事件对象到达目标元素
    3. 冒泡阶段Bubbling phase):事件对象从目标元素上开始往上冒泡,直至window
  • addEventListener默认false处于冒泡阶段,为true时,处于捕获阶段

  • 事实上,我们可以通过event对象来获取当前的阶段:eventPhase

  • 开发中通常会使用事件冒泡,所以事件捕获了解即可

三. 事件对象Event


1. Event对象

  • 当一个事件发生时,就会有和这个事件相关的很多信息:

    • 比如事件的类型是什么,你点击的是哪一个元素,点击的位置是哪里等等相关的信息
    • 那么这些信息会被封装到一个Event对象中,这个对象由浏览器创建,称之为event对象
    • 该对象给我们提供了想要的一些属性,以及可以通过该对象进行某些操作
  • 如何获取这个event对象呢?

    • event对象会在传入的事件处理(event handler)函数回调时,被系统传入

    • 我们可以在回调函数中拿到这个event对象

      js
      spanEl.onclick = function(event) {
        console.log('事件对象:', evnet)
      }

2. event常见的属性和方法

  • event对象常见的属性:

    属性含义
    type事件的类型
    target当前事件触发的元素
    currentTarget当前处理事件的元素
    eventPhase事件所处的阶段
    offsetXoffsetY事件发生在元素内的位置
    clientXclientY事件发生在客户端内的位置
    pageXpageY事件发生在页面中的位置
    screenXscreenY事件发生在屏幕的位置
  • 常见的方法:

    方法作用
    preventDefault取消事件的默认行为
    stopPropagation阻止事件的进一步传递(冒泡或捕获都可以阻止)

3. 事件处理中的this

  • 在函数中,我们也可以通过this来获取当前的发生元素

    js
    boxEl.addEventListener('click', function(event) {
      console.log(event.target === this) // true
    })
  • 这是因为在浏览器内部,调用event handler是绑定到当前的target上的

四. EventTarget使用


  • 我们会发现,所有的节点、元素都继承自EventTarget

    • 事实上Window也继承自EventTarget
  • 那么这个EventTarget是什么呢?

    • EventTarget是一个DOM接口,主要用于添加、删除、派发Event事件
  • EventTarget常见的方法:

    方法作用
    addEventListener注册某个事件类型以及事件处理函数
    removeEventListener移除某个事件类型以及事件处理函数
    dispatchEvent派发某个事件类型到EventTarget
    js
    var boxEl = document.querySelector('.box')
    
    boxEl.addEventListener('click', function () {
      console.log('box发生了点击')
      boxEl.dispatchEvent(new Event('later'))
    })
    
    boxEl.addEventListener('later', function () {
      console.log('触发了later事件')
    })

五. 事件委托模式


1. 事件委托(event delegation)

  • 事件冒泡在某种情况下可以帮助我们实现强大的事件处理模式 – 事件委托模式(也是一种设计模式

  • 那么这个模式是怎么样的呢?

    • 因为当子元素被点击时父元素可以通过冒泡可以监听到子元素的点击
    • 并且可以通过event.target获取到触发事件的元素
  • 案例:一个ul中存放多个li,点击某一个li会变成红色

    • 方案一:监听每一个li的点击,并且做出相应处理

      js
      var liEls = document.querySelectorAll("li")
      
      for (var liEl of liEls) {
        liEl.onclick = function(event) {
          event.currentTarget.classList.add("active")
        }
      }
    • 方案二:在ul中监听点击,并且通过event.target拿到对应的li进行处理

    • 因为这种方案并不需要遍历后给每一个li上添加事件监听,所以性能更好

      js
      var listEl = document.querySeletor('.list')
      
      var currentActive = null
      listEl.addEventListener('click', function(event) {
        if (currentActive) currentActive.classList.remove('active')
        event.target.classList.add('active')
        currentActive = event.target
      })

2. 事件委托的标记

  • 某些事件委托可能需要对具体的子组件进行区分,这个时候我们可以使用data-*对其进行标记:

  • 比如多个按钮的点击,区分点击了哪一个按钮:

    html
    <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>

六. 常见的事件


1. 常见的鼠标事件

  • 接下来我们来看一下常见的鼠标事件(不仅仅是鼠标设备,也包括模拟鼠标的设备,比如手机、平板电脑)

  • 常见的鼠标事件:

    属性描述
    click当用户点击某个对象时调用的事件句柄
    contextmenu在用户点击鼠标右键打开上下文菜单时触发
    dbclick当用户双击某个对象时调用的事件句柄
    mousedown鼠标按键被按下
    mouseup鼠标按键被松开
    mouseover鼠标移到某元素上(支持冒泡)
    mouseout鼠标从某元素移开(支持冒泡)
    mouseenter当鼠标指针移动到元素上时触发(不支持冒泡)
    mouseleave当鼠标指针移除元素时触发(不支持冒泡)
    mousemove鼠标被移动
  • 事件触发优先级:mousedown > mouseup > click

2. mouseover和mouseenter的区别

  • mouseentermouseleave

    • 不支持冒泡
    • 进入子元素依然属于在该元素内,没有任何反应
  • mouseovermosueout

    • 支持冒泡

    • 进入元素的子元素时

      • 先调用父元素的mouseout

      • 再调用子元素的mouseover

      • 因为支持冒泡,所以会将子元素的mouseover传递到父元素中

        image-20220524122034668

3. 常见的键盘事件

  • 常见的键盘事件:

    属性描述
    onkeydown某个按键被按下
    onkeypress某个按键被按下
    onkeyup某个按键被松开
  • 事件的执行顺序是 onkeydownonkeypressonkeyup

    • down事件先发生
    • press发生在文本被输入
    • up发生在文本输入完成
  • 我们可以通过keycode来区分按下的键:

    • code:“按键代码”("KeyA""ArrowLeft"等),特定于键盘上按键的物理位置
    • key:字符("A""a"等),对于非字符(non-character)的按键,通常具有与code相同的值)

4. 常见的表单事件

  • 针对表单也有常见的事件:

    属性描述
    onchange该事件在表单元素的内容改变时触发(<input>、<keygen>、<select>、<textarea>
    oninput元素获取用户输入时触发
    onfocus元素获取焦点时触发
    onblur元素失去焦点时触发
    onreset表单重置时触发
    onsubmit表单提交时触发

5. 文档加载事件

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

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

    html
    <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) // 200 100
      }
    </script>
    
    <div class="box">
      <p>哈哈哈啊</p>
    </div>
    <a href="#">百度一下</a>
    <img src="../images/kobe01.jpg" alt="">
  • 事件类型:https://developer.mozilla.org/zh-CN/docs/Web/Events

Released under the MIT License.