浏览器的事件处理
事件处理
事件处理方案
- 如何进行事件监听呢?
- 事件监听方式一:在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>
常见的事件列表
鼠标事件:
- click —— 当鼠标点击一个元素时(触摸屏设备会在点击时生成)。
 - mouseover / mouseout —— 当鼠标指针移入/离开一个元素时。
 - mousedown / mouseup —— 当在元素上按下/释放鼠标按钮时。
 - mousemove —— 当鼠标移动时。
 
键盘事件:
- keydown 和 keyup —— 当按下和松开一个按键时。
 
表单(form)元素事件:
- submit —— 当访问者提交了一个 
<form>时。 - focus —— 当访问者聚焦于一个元素时,例如聚焦于一个 
<input>。 
- submit —— 当访问者提交了一个 
 Document 事件:
- DOMContentLoaded —— 当 HTML 的加载和处理均完成,DOM 被完全构建完成时。
 
CSS 事件:
- transitionend —— 当一个 CSS 动画完成时。
 
认识事件流
- 事实上对于事件有一个概念叫做事件流,为什么会产生事件流呢?
 - 我们可以想到一个问题:当我们在浏览器上对着一个元素点击时,你点击的不仅仅是这个元素本身;
 - 这是因为我们的HTML元素是存在父子元素叠加层级的;
 - 比如一个span元素是放在div元素上的,div元素是放在body元素上的,body元素是放在html元素上的;
 

事件冒泡和事件捕获
我们会发现默认情况下事件是从最内层的span向外依次传递的顺序,这个顺序我们称之为事件冒泡(Event Bubble);
事实上,还有另外一种监听事件流的方式就是从外层到内层(body -> span),这种称之为事件捕获(Event Capture);
为什么会产生两种不同的处理流呢?
- 这是因为早期浏览器开发时,不管是IE还是Netscape公司都发现了这个问题;
 - 但是他们采用了完全相反的事件流来对事件进行了传递;
 - IE采用了事件冒泡的方式,Netscape采用了事件捕获的方式;
 
那么我们如何去监听事件捕获的过程呢?

事件捕获和冒泡的过程
如果我们都监听,那么会按照如下顺序来执行:
- 捕获阶段(Capturing phase): 事件(从 Window)向下走近元素。
 - 目标阶段(Target phase): 事件到达目标元素。
 - 冒泡阶段(Bubbling phase): 事件从元素上开始冒泡。
 
事实上,我们可以通过event对象来获取当前的阶段:
- eventPhase
 
开发中通常会使用事件冒泡,所以事件捕获了解即可。
<body>
  
  <div class="box">
    <span></span>
  </div>
  <script>
    // 1.获取元素
    var spanEl = document.querySelector("span")
    var divEl = document.querySelector("div")
    var bodyEl = document.body
    // 默认情况下是事件冒泡
    spanEl.addEventListener("click", function() {
      console.log("span元素发生了点击~冒泡")
    })
    divEl.addEventListener("click", function() {
      console.log("div元素发生了点击~冒泡")
    })
    bodyEl.addEventListener("click", function() {
      console.log("body元素发生了点击~冒泡")
    })
    // 设置希望监听事件捕获的过程
    spanEl.addEventListener("click", function() {
      console.log("span元素发生了点击~捕获")
    }, true)
    divEl.addEventListener("click", function() {
      console.log("div元素发生了点击~捕获")
    }, true)
    bodyEl.addEventListener("click", function() {
      console.log("body元素发生了点击~捕获")
    }, true)
  </script>
</body>

事件对象
event对象会在传入的事件处理(event handler)函数回调时,被系统传入;
我们可以在回调函数中拿到这个event对象;
常见的属性:
- type:事件的类型;
- 处于捕获的阶段是1
 - 本身就是2
 - 处于冒泡阶段是3
 
 - target:当前事件发生的元素;
 - currentTarget:当前处理事件的元素;
 - eventPhase:事件所处的阶段;
 - offsetX、offsetY:事件发生在元素内的位置;
 - clientX、clientY:事件发生在客户端内的位置;
- 如果有滚动也不会计算,就是相对于视口
 
 - pageX、pageY:事件发生在客户端相对于document的位置;
- 如果有滚动,会计算滚动的距离
 
 - screenX、screenY:事件发生相对于屏幕的位置;
- 相对于电脑屏幕的距离,不是浏览器的距离
 
 
- type:事件的类型;
 常见的方法:
- preventDefault:取消事件的默认行为;
 - stopPropagation:阻止事件的进一步传递(冒泡或者捕获都可以阻止);
 
<body>
 <div> <a href="http://www.baidu.com">百度一下</a></div>
  <div class="box">
    <span class="btn">
      <button>按钮</button>
    </span>
  </div>
  
  <script>
    var divEl = document.querySelector("div")
    var btnEl = document.querySelector("button")
    var spanEl = document.querySelector("span")
    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)
    }
   // 阻止默认行为
    var aEl = document.querySelector("a")
    aEl.onclick = function(event) {
      console.log("a元素发生了点击~")
      event.preventDefault()
    }
    //阻止冒泡
     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>
事件处理中的this
- 在函数中,我们也可以通过this来获取当前的发生元素:
 - 这是因为在浏览器内部,调用event handler是绑定到当前的target上的
 
<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)
    }
  </script>
</body>
EventTarget类
我们会发现,所有的节点、元素都继承自EventTarget
事实上Window也继承自EventTarget;
那么这个EventTarget是什么呢?
- EventTarget是一个DOM接口,主要用于添加、删除、派发Event事件;
 
EventTarget常见的方法:
- addEventListener:注册某个事件类型以及事件处理函数;
 - removeEventListener:移除某个事件类型以及事件处理函数;
- 移除时,必须传入原先监听的函数,不传的话,会移除失败
 
 - dispatchEvent:派发某个事件类型到EventTarget上;
 
    // 这种做法是无法移除的
    // btnEl.addEventListener("click", function() {
    //   console.log("btn监听的处理函数~")
    // })
    // setTimeout(function() {
    //   btnEl.removeEventListener("click", function() {})
    // }, 5000)
    //这种做法才可以移除
    var foo = function() {
      console.log("监听到按钮的点击")
    }
    btnEl.addEventListener("click", foo)
    // 需求: 过5s钟后, 将这个事件监听移除掉
    setTimeout(function() {
      btnEl.removeEventListener("click", foo)
    }, 5000)
window.addEventListener("coderwhy", function() {
  console.log("监听到coderwhy的呼唤~")
})
setTimeout(function() {
  // dispatchEvent可以实现类似于事件总线的效果
  window.dispatchEvent(new Event("coderwhy"))
}, 5000)
事件委托(event delegation)
事件冒泡在某种情况下可以帮助我们实现强大的事件处理模式
- 事件委托模式(也是一种设计模式)
 
那么这个模式是怎么样的呢?
- 因为当子元素被点击时,父元素可以通过冒泡可以监听到子元素的点击;
 - 并且可以通过event.target获取到当前监听的元素;
 
案例:一个ul中存放多个li,点击某一个li会变成红色
- 方案一:监听每一个li的点击,并且做出相应;
 - 方案二:在ul中监听点击,并且通过event.target拿到对应的li进行处理;
- 因为这种方案并不需要遍历后给每一个li上添加事件监听,所以它更加高效;
 
 
常见的鼠标事件
接下来我们来看一下常见的鼠标事件(不仅仅是鼠标设备,也包括模拟鼠标的设备,比如手机、平板电脑)
常见的鼠标事件:

mouseover和mouseenter的区别
mouseenter和mouseleave
- 不支持冒泡
 - 进入子元素依然属于在该元素内,没有任何反应
 
mouseover和mouseout
- 支持冒泡
 - 进入元素的子元素时
- 先调用父元素的mouseout
 - 再调用子元素的mouseover
 - 因为支持冒泡,所以会将mouseover传递到父元素中;
 
 
<body>
  
  <div class="box">
    <button>删除</button>
    <button>新增</button>
    <button>搜索</button>
  </div>
  <script>
    // 方案一: 监听的本身就是button元素,用mouseenter和mouseover都可以。
    // var btnEls = document.querySelectorAll("button")
    // for (var i = 0; i < btnEls.length; i++) {
    //   btnEls[i].onmouseenter = function(event) {
    //     console.log(event.target.textContent)
    //   }
    // }
    // 方案二: 事件委托,只能用mouseover,它支持冒泡
    var boxEl = document.querySelector(".box")
    boxEl.onmouseover = function(event) {
      console.log(event.target.textContent)
    }
    
  </script>
</body>
常见的键盘事件
 常见的键盘事件:
事件的执行顺序是
- onkeydown、onkeypress、onkeyup down事件先发生;
 - press发生在文本被输入;
 - up发生在文本输入完成时,或者说按键抬起时;
 
我们可以通过key和code来区分按下的键:
- code:“按键代码”("KeyA","ArrowLeft" 等),特定于键盘上按键的物理位置。
 - key:
- 对于非字符(non-character)的按键,通常具有与 code 相同的值。
 - 对于字符("A","a" 等),可以区分大小写。
 
 
<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>
常见的表单事件
针对表单也有常见的事件:

<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>
文档加载事件
DOMContentLoaded:
- 浏览器已完全加载 HTML,并构建了 DOM 树,但像 
<img>和样式表之类的外部资源可能尚未加载 完成。 
- 浏览器已完全加载 HTML,并构建了 DOM 树,但像 
 load:
- 浏览器不仅加载完成了 HTML,还加载完成了所有外部资源:图片,样式等。
 
 <style>
    .box {
      width: 200px;
      height: 200px;
    }
  </style>
<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)
    })
    window.onload = function() {
      console.log("文档中所有资源都加载完毕")
      // var imgEl = document.querySelector("img")
      // console.log("图片的宽度和高度:", imgEl.offsetWidth, imgEl.offsetHeight)
    }
    window.onresize = function() {
      console.log("创建大小发生改变时")
    }
  </script>
  <div class="box">
    <p>哈哈哈啊</p>
  </div>
  <a href="#">百度一下</a>
  <img src="../images/kobe01.jpg" alt="">
</body>
MDN事件文档
更多事件可以参考以下:
