事件:系统内发生的动作或事情,会产生或触发某种信号,所有的 DOM 节点都生成这样的信号
事件处理程序:响应事件的的一个处理程序,在事件发生时运行的函数

事件处理程序设置方法

1. HTML 特性

在标签中添加 on<event> attribute(不区分大小写):

<input value="Click me" onclick="alert('Click!')" type="button">

2. DOM 属性

在 JS 里给元素对象的 on<event> 赋值(区分大小写)(只能为同一个事件分配一个处理程序):

elem.onclick = function() { alert('Click!') }

移除处理程序:elem.onclick = null

注意

如果将一个现存的函数赋值给处理程序,在 JS 里不加括号,在标签上要加括号

因为在 JS 里加括号则表示调用,会把函数返回值赋值进去,
而当浏览器读取 HTML attribute 时,会使用 赋值的内容 创建一个处理程序,自动包一层 function

function hello() {
  alert('hello!')
}
 
// JS
elem.onclick = hello // 不要用 elem.onclick = hello()
 
// 标签
<button id="button" onclick="hello()"></button>
// 相当于
// button.onclick = function(event) {
//   hello() // <-- attribute 中的内容变到了这里
// }

3. addEventListener 方法

可以为同一个事件分配多个处理程序,同一个事件的运行顺序和创建顺序相同:element.addEventListener (event, handler[, options])

  • event:事件名,如:“click”
  • handler:处理程序
  • options:具有以下属性的附加可选对象:
    • once:如果为 true,那么会在被触发后自动删除监听器
    • capture:事件处理的阶段,由于历史原因,options 也可以是 false/true,它与 {capture: false/true} 相同
    • passive:如果为 true,表示约定处理程序将不会调用 preventDefault(),如果里面还是调用了,会抛出警告(用于性能优化)
    • signal:该 AbortSignal 的 abort() 方法被调用时,监听器会被移除

移除处理程序:element.removeEventListener (event, handler[, options])

注意传入的 event,handler 和 capture 要一样才能移除。如:事件处理函数要用引用传入,保证与创建时一样才能移除,而且要同一冒泡/捕获阶段

Tip

handler 除了是函数外,还可以是一个对象。只要这个对象里有 handleEvent 方法,当事件发生时就会调用这个方法

NOTE

  • 有些事件无法通过 DOM 属性进行分配,只能使用 addEventListener,所以 addEventListener 更通用
  • 无论 addEventListener 怎样,on<event> 处理程序都会触发

event

当事件发生时,浏览器会创建一个 event 对象,将详细信息放入其中,并将其作为参数传递给处理程序

  • type:事件类型
  • event.target:触发事件的元素,不会发生变化
  • event.currentTarget:处理事件的元素,会在冒泡/捕获时发生变化
  • event.eventPhase:当前处于哪个 事件阶段
  • event.defaultPrevented:用于确定事件的默认行为是否已被取消(调用过 event.preventDefault() 方法)

this

处理程序中的 this 的值是当前的元素,也就是处理程序所在的那个元素 (this === event.currentTarget)

事件阶段

事件传播有三个阶段:

  1. 捕获阶段(CAPTURING_PHASE:1):事件从 window 向下传入目标元素
  2. 目标阶段(AT_TARGET:2):事件到达目标元素
  3. 冒泡阶段(BUBBLING_PHASE:3):事件从目标元素向上传到 window

捕获

很少使用,上面添加事件处理程序的几种方式都默认只是在 目标阶段 和 冒泡阶段
如果要在捕获阶段触发,则设置 addEventListener 的 capture 为 true

冒泡

当一个事件发生在一个元素上,它会首先运行在该元素上的处理程序,然后运行其父元素上的处理程序,然后一直向上到其他祖先上的处理程序

事件委托

如果有多个元素需要相同的处理程序,那么可以不用每个元素都写一个处理程序,而是在它们的共同祖先元素上绑定事件,然后在处理程序里判断 event.target 进行不同的处理

优点:

  • 节省内存
  • 更少的代码
  • 可以方便地进行内层的 DOM 修改

缺点:

  • 事件必须冒泡,而有些事件不会冒泡或阻止了冒泡
  • 可能会增加 CPU 负载,因为容器级别的处理程序会对容器中任意位置的事件做出反应,但是一般忽略不计

停止传播(停止冒泡和停止捕获)

  • event.stopPropagation():停止当前事件继续传播
  • stopImmediatePropagation():停止元素上所有事件的继续传播

通常,没有真正必要去阻止传播。一项看似需要阻止传播的任务,可以通过其他方法解决,其中之一就是使用自定义事件

浏览器默认行为

很多事件会自动触发浏览器执行某些行为:

  • mousedown:在文本上按下鼠标按钮并移动:选中文本
  • <input type="checkbox">click:选中/取消选中 input
  • 点击 <input type="submit"> 或者在表单字段中按下 Enter 键:浏览器将提交表单,触发提交到服务器的行为
  • 点击链接自动跳转到对应 url
  • 鼠标右键单击时,显示浏览器上下文菜单

阻止浏览器默认行为:

  1. 主流做法:event.preventDefault()
  2. 如果处理程序是使用 on<event>(而不是 addEventListener)分配的,需要返回 false(其他情况下处理程序的返回值没有任何意义)

注意 on<event> 要接受到 false:

<script>
  function handler() {
    alert("...")
    return false
  }
</script>
 
<a href="https://w3.org" onclick="return handler()">w3.org</a>

Tip

有些事件会自动触发另一个事件,如果阻止了会一并阻止