事件处理的多种绑定方式

1,662 阅读7分钟

前端的交互基于一系列的事件绑定

在 Web 中, 事件在浏览器窗口中被触发并且通常被绑定到窗口内部的特定部分 — 可能是一个元素、一系列元素、被加载到这个窗口的 HTML 代码或者是整个浏览器窗口

例如

  • 用户在某个元素上点击鼠标
  • 用户在键盘中按下某个按键
  • 用户调整浏览器的大小或者关闭浏览器窗口

事件类型

用来说明发生说明类型事件的字符串

具体事件类型请看 developer.mozilla.org/zh-CN/docs/…

事件目标

要绑定事件的相关对象即注册事件处理程序的目标对象

事件监听程序(注册事件处理程序)

1.设置javascript对象属性为事件处理程序

注册事件处理程序最简单的方式就是通过设置事件目标的属性为所需事件处理程序函数,按照约定,事件处理程序属性的名字由'on'+事件名组成。例如:onclick,onmousedown。属性名区分大小写,全部都是小写。

window.onload = function () {
  const elt = document.getElementById('id')
  elt.onclick = function () {
    console.log('点击')
  }
}

缺点:最多只有一个处理程序,后面的处理程序会覆盖前面的处理程序

2.设置html标签属性为事件处理程序

在html标签的属性上设置事件处理程序,属性值应该是javascript代码字符串,这段代码应该是事件处理程序函数的主体,而不是完整的函数声明。即事件处理程序代码没有大括号包围且没有function关键字。

<button onclick="bgChange()">Press me</button>

function bgChange(e) {
  console.log(e);
  document.body.style.backgroundColor = 'red';
}

缺点:无法获取event对象,且最多只有一个处理程序。(MDN中建议请勿使用这种方式)

在vue,react前端框架中,我们经常看到在html标签属性上绑定事件

框架中这样处理,是因为框架已经做了封装处理,能获取event对象,同时有以下好处

event-html.png

event-on.png

event-on.png

3.addEventListener()绑定事件处理程序

EventTarget.addEventListener() 方法将指定的监听器注册到 EventTarget 上,当该对象触发指定的事件时,指定的回调函数就会被执行。

语法

target.addEventListener(type, listener[, options]);
target.addEventListener(type, listener[, useCapture]);
target.addEventListener(type, listener[, useCapture, wantsUntrusted  ]);

参数

  • type - 表示监听事件类型的字符串。请注意,您不要为事件使用"on"前缀; 用"click"而不是"onclick"。
  • listener - 当所监听的事件类型触发时,触发的事件处理程序。
  • options - 第三个参数说明:

1.若是布尔值的话, useCapture 默认为 false ,表示冒泡阶段 2.若是对象的话:一个指定有关 listener 属性的可选参数对象。可用的选项如下:

  • capture: Boolean: false,表示 listener 会在该类型的事件捕获阶段传播到该 EventTarget 时触发。
  • once: Boolean: false,表示 listener 在添加之后最多只调用一次。如果是 true, listener 会在其被调用之后自动移除。
  • passive: Boolean:false,表示 listener 永远不会调用 preventDefault()。如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。(passive: true 可用于改善滚屏性能)

优点

  • 1.它允许给一个事件注册多个监听器。
  • 2.它提供了一种更精细的手段控制 listener 的触发阶段。(即可以选择捕获或者冒泡)。
  • 3.它对任何 DOM 元素都是有效的,而不仅仅只对 HTML 元素有效。

以下例子是对options参数的说明示例

<!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>
</head>
<style>
  .outerStyle{
    width:520px;
    padding:15px;
    margin:15px;
    border:1px solid red;
    color:red;
  }
  .outerStyle1{
    width:520px;
    padding:15px;
    margin:15px;
    border:1px solid green;
    color:green;
  }
  .outerStyle2{
    width:520px;
    padding:15px;
    margin:15px;
    border:1px solid purple;
    color:purple;
  }
</style>
<body>
  <div class="once outerStyle">
    once: true 只调用一次
  </div>
  <div class="onceNone outerStyle">
    onceNone: false 可调用多次
  </div>
  <div class="capture outerStyle1">
    capture 捕获阶段 & 冒泡阶段
  </div>
  <div class="passive outerStyle2">
    passive: true 表示 listener 永远不会调用 preventDefault(),如果调用了则抛出警告
  </div>
  <div class="passiveNone outerStyle2">
    passive: false 表示 listener 可以调用 preventDefault()
  </div>
</body>
<script>
  let once  = document.getElementsByClassName('once')[0]
  let onceNone  = document.getElementsByClassName('onceNone') [0]
  // once: Boolean,表示 listener 在添加之后最多只调用一次。如果是 true, listener 会在其被调用之后自动移除。
  once.addEventListener('click', onceHandler, {once: true})
  function onceHandler(event) {
    alert('once: true 只调用一次')
  }
  onceNone.addEventListener('click', noneOnceHandler, {once: false})
  function noneOnceHandler(event){
    alert('onceNone: false 可调用多次')
  }

  let capture  = document.getElementsByClassName('capture')[0]
  // capture: Boolean,表示 listener 会在该类型的事件捕获阶段还是冒泡阶段时触发。捕获阶段先触发,冒泡阶段后触发
  capture.addEventListener('click', captureHandler, {capture: true})
  function captureHandler(event) {
    alert('capture: true 捕获阶段')
  }
  capture.addEventListener('click', noneCaptureHandler, {capture: false})
  function noneCaptureHandler(event) {
    alert('capture: false 冒泡阶段')
  }

  let passive  = document.getElementsByClassName('passive')[0]
  let passiveNone  = document.getElementsByClassName('passiveNone') [0]
  // passive: Boolean,表示 listener 永远不会调用 preventDefault()。如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。
  passive.addEventListener('click', passiveHandler, {passive: true})
  function passiveHandler(event) {
    // Unable to preventDefault inside passive event listener invocation.
    event.preventDefault()
    alert('passive: true 表示 listener 永远不会调用 preventDefault(),如果调用了则抛出警告')
  }
  passiveNone.addEventListener('click', nonePassiveHandler, {passive: false})
  function nonePassiveHandler(event) {
    event.preventDefault()
    alert('passive: false 表示 listener 可以调用 preventDefault()')
  }
</script>
</html>

捕获冒泡阶段DOM图

eventflow.svg

给一个事件注册多个监听器

  • 1.允许向同一元素的同一个事件类型添加不同的事件处理程序
  • 2.允许向同一元素的不同事件类型添加同一个事件处理程序
  • 3.如果同一个元素的同一个事件类型添加同一个事件处理程序,则重复的会被自动抛弃(覆盖),不用手动清除
  let doubleBind  = document.getElementsByClassName('doubleBind')[0]
  doubleBind.addEventListener('click', doubleBind1, false)
  function doubleBind1(event) {
    alert('doubleBind1')
  }
  doubleBind.addEventListener('click', doubleBind2, false)
  function doubleBind2(event) {
    alert('doubleBind2')
  }
  doubleBind.addEventListener('mouseup', doubleBind1, false)
  doubleBind.addEventListener('click', doubleBind2, false)

removeEventListener移除绑定的事件

  • 1.removeEventListener()的参数定义与addEventListener()一模一样
  • 2.通过addEventListener()添加的事件处理程序只能使用removeEventListener()来移除
  • 3.移除时传入的参数与添加处理程序时使用的参数,前两个参数要一样,最后一个参数只有capture配置影响removeEventListener()
  doubleBind.addEventListener('click', doubleBind1, false)

  // 第一个移除成功是因为最后一个参数useCapture匹配成功,第二个移除失败是因为最后一个参数匹配失败
  doubleBind.removeEventListener('click', doubleBind1, false) // 移除成功
  doubleBind.removeEventListener('click', doubleBind1, true) // 移除失败

  // 最后一个参数如果是options对象形式的话,只有capture属性影响移除
  doubleBind.removeEventListener('click', doubleBind1, {capture: false}) // 移除成功
  doubleBind.removeEventListener('click', doubleBind1, {capture: true}) // 移除失败
  doubleBind.removeEventListener('click', doubleBind1, {passive: false}) // 移除成功
  doubleBind.removeEventListener('click', doubleBind1, {passive: true}) // 移除成功
  doubleBind.removeEventListener('click', doubleBind1, {once: false}) // 移除成功
  doubleBind.removeEventListener('click', doubleBind1, {once: true}) // 移除成功
  • 4.通过addEventListener()添加的匿名函数无法移除,因为在removeEventListener中写了一样的匿名函数,但是两个方法并不相等,内存地址已经是不同的
  doubleBind.addEventListener('click', () => { doubleBind1() }, false)
  doubleBind.removeEventListener('click', () => { doubleBind1() }, false) // 移除失败
  • 5.EventListener被移除之后,如果此事件正在执行,会立即停止。EventListener移除之后不能被触发,但可以重新绑定
  • 6.在EventTarget上使用任何未识别当前注册的EventListener调用removeEventListener()不会起任何作用

事件对象

事件对象作为参数传递给事件处理程序函数,是与特定事件相关且包含有关该事件详细信息的对象

更多信息请看 juejin.cn/post/698755…

创建自定义事件

Event构造器

最简单的创建事件方法,是使用Event构造器

let myEvent = new Event('event_name')

// event = new Event(typeArg, eventInit);

参数

  • typeArg 是DOMString 类型,表示所创建事件的名称。

  • eventInit可选 是 EventInit 类型的字典,接受以下字段:

    • "bubbles",可选,Boolean类型,默认值为 false,表示该事件是否冒泡。
    • "cancelable",可选,Boolean类型,默认值为 false, 表示该事件能否被取消。
    • "composed",可选,Boolean类型,默认值为 false,指示事件是否会在影子DOM根节点之外触发侦听器。

CustomEvent构造器

为了能够传递数据,就需要使用CustomEvent构造器

let myEvent = new CustomEvent('event_name', {
  detail: {
    // 将需要传递的数据写在detail中,以便在EventListener中获取
    // 数据将会在event.detail中得到
  }
})

// event = new CustomEvent(typeArg, customEventInit);

参数

  • typeArg 是DOMString 类型,表示所创建事件的名称。
  • customEventInit可选 是 EventInit 类型的字典,接受以下字段:
    • "detail", 可选的默认值是 null 的任意类型数据,是一个与 event 相关的值
    • "bubbles", 可选,Boolean类型,默认值为 false,表示该事件是否冒泡。
    • "cancelable", 可选,Boolean类型,默认值为 false, 表示该事件能否被取消。

事件监听

JS的EventListener是根据事件的名称来进行监听的,比如我们在上文中已经创建了一个名称为‘event_name’ 的事件,那么当某个元素需要监听它的时候,就需要创建相应的监听器:

// 在dom元素上注册listener事件
myDiv.addEventListener('event_name', function(event){
    // 如果是CustomEvent,传入的数据在event.detail中
    console.log('得到数据为:', event.detail);
    // ...后续相关操作
});

事件触发

对于内置事件,通常都是一些操作去触发,比如鼠标点击事件等。自定义事件由于不是js内置事件,所以需要在js代码中显示地触发。方法是使用dispatchEvent触发

myDiv.dispatchEvent(myEvent)

完整示例

myDiv.addEventListener("hello", function(event) {
  alert(event.detail.name);
});

let myEvent = new CustomEvent("hello", {
  detail: { name: "wuqinhao" }
})
myDiv.dispatchEvent(myEvent);