一、认识事件处理

web 页面需要和用户之间进行交互,而交互的过程中需要捕捉交互的过程;

比如用户点击了某个按钮、用户在输入框里面输入了某个文本、用户鼠标经过了某个位置;

浏览器需要搭建一条 JavaScript 代码和事件之间的桥梁;

当某个事件发生时,让 JavaScript 可以响应(执行某个函数),所以需要针对事件编写处理程序。

监听方式

  • 在 JavaScript 中直接监听

    1
    
    <button onclick="console.log('按钮发生了点击')">按钮</button>
    
  • dom 属性,通过元素的 on 来监听

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    <button class="btn">按钮</button>
    
    <script>
      var btnEl = document.querySelector(".btn")
    
      btnEl.onclick = function () {
        console.log("按钮发生了点击")
      }
    </script>
    
  • 通过 EventTarget 的 addEventListener 来监听

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    <button class="btn">按钮</button>
    
    <script>
      var btnEl = document.querySelector(".btn")
    
      btn1El.addEventListener("click", function () {
        console.log("按钮发生了点击1")
      })
    
      btn2El.addEventListener("click", function () {
        console.log("按钮发生了点击2")
      })
    </script>
    

二、事件冒泡和事件捕获

事件流

在浏览器上对一个元素点击时,点击的不仅仅是这个元素本身.

因为 html 元素是存在父子元素叠加层级的,比如 span 元素是放在 div 元素上的,div 元素是放在 body 元素上的,body 元素是放在 html 元素上的。

冒泡

最内层往最外层传递

捕获

最外层往最内层传递

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<style>
  .box {
    display: flex;
    justify-content: center;
    align-items: center;
    width: 200px;
    height: 200px;
    background-color: teal;
  }

  .box span {
    width: 100px;
    height: 100px;
    background: tomato;
  }
</style>

<div class="box">
  <span></span>
</div>

<script>
  var bodyEl = document.body
  var divEl = document.querySelector("div")
  var spanEl = document.querySelector("span")

  // 事件冒泡
  bodyEl.onclick = function () {
    console.log("body元素发生了点击")
  }
  divEl.onclick = function () {
    console.log("div元素发生了点击")
  }
  spanEl.onclick = function () {
    console.log("span元素发生了点击")
  }

  // 事件捕获
  bodyEl.addEventListener("click", function () {
    console.log("body元素发生了点击")
  }, true)
  divEl.addEventListener("click", function () {
    console.log("div元素发生了点击")
  }, true)
  spanEl.addEventListener("click", function () {
    console.log("span元素发生了点击")
  }, true)
</script>

三、事件对象event

常见属性

  • type: 事件类型
  • target: 当前事件发生的元素
  • currentTarget: 当前处理事件的元素
  • eventPhase: 事件所处的阶段
  • offsetX, offsetY: 事件发生在元素内的位置
  • clientX, clientY: 事件发生在客户端内的位置
  • pageX, pageY: 事件发生在客户端相对于 document 的位置
  • screenX, screenY: 事件发生相对于 的位置
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<style>
  .box {
    width: 200px;
    height: 200px;
    background-color: teal;
  }
</style>

<div class="box"></div>

<script>
  var boxEl = document.querySelector(".box")

  boxEl.onclick = function (event) {
    console.log("事件类型", event.type)
    console.log("事件阶段", event.eventPhase)
    console.log("事件元素中的位置", event.offsetX, event.offsetY)
    console.log("事件客户端中的位置", event.clientX, event.clientY)
    console.log("事件页面的位置", event.pageX, event.pageY)
    console.log("事件屏幕的位置", event.screenX, event.screenY)
    console.log(event.target, event.currentTarget, event.target === event.currentTarget)
  }
</script>

常见方法

  • preventDefault: 阻止默认行为

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    <a href="https://www.baidu.com">百度一下</a>
    
    <script>
      /* 阻止默认行为 */
      aEl.onclick = function (event) {
        console.log("a元素发生了点击")
        event.preventDefault()
      }
    </script>
    
  • stopPropagation: 阻止事件进一步传递冒泡和捕获都可以阻止

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    
    <div class="box">
      <span>
        <button>按钮</button>
      </span>
    </div>
    
    <script>
      var btnEl = document.querySelector("button")
      var spanEl = document.querySelector("span")
      var divEl = document.querySelector("div")
    
      btnEl.addEventListener("click", function (event) {
        console.log("btn事件捕获")
      }, true)
    
      spanEl.addEventListener("click", function () {
        console.log("span事件捕获")
      }, true)
    
      divEl.addEventListener("click", function () {
        console.log("div事件捕获")
        event.stopPropagation()
      }, true)
    
      btnEl.addEventListener("click", function () {
        console.log("btn事件冒泡")
      })
    
      spanEl.addEventListener("click", function () {
        console.log("span事件冒泡")
      })
    
      divEl.addEventListener("click", function () {
        console.log("div事件冒泡")
      })
    </script>
    
  • 事件处理函数中的this就是处理的元素

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    <button>按钮</button>
    
    <script>
      var btnEl = document.querySelector("button")
    
      btnEl.onclick = function (event) {
        console.log(this)
        console.log(event.target)
        console.log(event.currentTarget)
      }
    </script>
    

四、EventTarget使用

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

EventTarget 是 DOM 接口,用于添加、删除、派发 Event。

常见方法

  • addEventListener: 注册某个事件类型以及事件处理函数
  • removeEventListener: 移除某个事件类型以及事件处理函数
  • dispatchEvent: 派发某个事件类型到 EventTarget 上
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<button>按钮</button>

<script>
  var btnEl = document.querySelector("button")

  var foo = function () {
    console.log("监听到按钮点击")
  }

  // 注册
  btnEl.addEventListener("click", foo)

  // 移除 
  setTimeout(function (param) {
    btnEl.removeEventListener("click", foo)
  }, 5000)

  // 派发
  window.addEventListener("mocha", function () {
    console.log("监听到mocha的呼唤~")
  })

  setTimeout(function () {
    window.dispatchEvent(new Event("mocha"))
  }, 5000)
</script>

五、事件委托模式

当子元素被点击时,父元素可以通过冒泡监听到子元素的点击,并且可以通过 event.target 获取到当前监听的元素。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<style>
  .active {
    color: red;
    font-size: 20px;
    background: orange;
  }
</style>

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

<stript>
  var ulEl = document.querySelector("ul")

  // 记录当前被点击的元素
  var activeLiEl = null

  ulEl.onclick = function (event) {
	if (event.target !== ulEl) {
	  activeLiEl && activeLiEl.classList.remove("active")
	  event.target.classList.add("active")
	  activeLiEl = event.target
	}
  }
</stript>

案例练习

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<div class="box">
  <button data-action="remove">移除</button>
  <button data-action="new">新建</button>
  <button data-action="search">搜索</button>
  <button data-action="add">增加</button>
</div>

<script>
  var boxEls = document.querySelector(".box")
    boxEls.onclick = function (event) {
      if (event.target !== boxEls) {
        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>

六、常见的事件

鼠标事件

  • click: 点击某个对象时调用的事件句柄
  • contextMenu: 点击鼠标右键打开上下文菜单时触发
  • dblclick: 双击某个对象时调用的事件句柄
  • mousedown: 鼠标按钮被按下
  • mouseup: 鼠标按钮被松开
  • mouseover: 鼠标移到某个元素上,支持冒泡
  • mouseout: 鼠标从某个元素移开,支持冒泡
  • mouseenter: 当鼠标指针移动到元素上时触发,不支持冒泡
  • mouseleave: 当鼠标指针移出元素时触发,不支持冒泡
  • mousemove: 鼠标被移动
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<style>
  .box {
    width: 200px;
    height: 200px;
    background-color: orange;
  }
</style>

<div class="box"></div>

<script>
  var boxEl = document.querySelector(".box")
 
  boxEl.onclick = function () {
    console.log("点击")
  }

  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("移动")
    }
  }
</script>

mouseovermouseoutmouseentermouseleave的用法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<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>

<div class="box">
  <span></span>
</div>

<script>
  var boxEl = document.querySelector(".box")
  var spanEl = document.querySelector("span")

  // 支持冒泡
  boxEl.onmouseover = function () {
    console.log("boxEl onmouseover")
  }
    
  boxEl.onmouseout = function () {
    console.log("boxEl onmouseout")
  }

  // 不支持冒泡
  spanEl.onmouseover = function () {
    console.log("spanEl onmouseover")
  }
    
  spanEl.onmouseout = function () {
    console.log("spanEl onmouseout")
  }
</script>

键盘事件

  • onkeydown: 某个键盘按键被按下先发生
  • onkeypress: 某个键盘按键被按下发生在文本被输入
  • onkeyup: 某个键盘按键被松开发生在文本输入完成(抬起、松开)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<input type="text">

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

  inputEl.onkeydown = function () {
    console.log("onkeydown")
  }

  inputEl.onkeypress = function () {
    console.log("onkeypress")
  }

  inputEl.onkeyup = function (event) {
    if (event.key === "Enter") {
      console.log(inputEl.value)
    }
  }

  document.onkeyup = function (event) {
    if (event.code === "KeyS") {
      inputEl.focus()
    }
  }
</script>

表单事件

  • onchange: 表单元素的内容改变时触发
  • oninput: 元素获取用户输入时触发
  • onfoucs: 元素获取焦点时触发
  • onblur: 元素失去焦点时触发
  • onreset: 表单重置时触发
  • onsubmit: 表单提交时触发
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<form action="">
  <input type="text">
  <textarea name="" id="" cols="30" rows="10"></textarea>
  <button type="reset">重置</button>
  <button type="submit">提交</button>
</form>

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

  inputEl.onfocus = function () {
    console.log("获取焦点")
  }

  inputEl.onblur = function () {
    console.log("失去焦点")
  }

  inputEl.oninput = function () {
    console.log("正在输入内容:", inputEl.value)
  }

  inputEl.onchange = function () {
    console.log("内容发生了改变 :", inputEl.value)
  }

  formEl.onreset = function () {
    console.log("重置")
  }

  formEl.onsubmit = function (event) {
	console.log("提交")
	event.preventDefault();
	// axios ->
  }
</script>

文档加载事件

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

    1
    2
    3
    
    window.addEventListener("DOMContentLoaded", function () {
      console.log("html内容加载完毕")
    })
    
  • load: 浏览器不仅加载完成了 HTML,还加载完了所有外部资源,图片,样式等

    1
    2
    3
    
    window.addEventListener("loaad", function () {
      console.log("文档中所有的资源都加载完毕")
    })