事件是某事发生的信号,所有的 DOM 节点都生成这样的信号(但事件不仅限于 DOM)

DOM Level 规范迭代

DOM Level 0

DOM 没有被 W3C 定为标准之前

DOM Level 1

DOM 成为 W3C 的标准后,称为 DOM Level 1

DOM Level 1由两个模块组成:

DOM Level 2

在 DOM Level 1 的基础上进行了扩展。为节点添加了更多方法和属性等

添加新的模块,包括:视图、事件、范围、遍历、样式等

DOM Level 3

进一步扩展了 DOM,增加了 XPath 模块、加载和保存(DOM Load and Save)模块等,开始支持 XML1.0 规范

事件监听

attribute、property

DOM Level 0 事件

处理程序可以设置在 HTML 中名为 on<event> 的 attribute 中:

<!-- 直接写 JS 逻辑 -->
<input value="Click me" onclick="alert('Click!')" type="button">

<!-- 指向一个 JS 函数 -->
<input value="Click me" onclick="handleClick()" type="button">
<script>
function handleClick() {
  alert('Click!')
}
</script>

如果一个处理程序是通过 HTML 特性(attribute)分配的,那么随后浏览器读取它,并从特性的内容创建一个新函数,并将这个函数写入 DOM 属性(property)

因此,这种方法实际上与前一种方法相同:

<input id="elem" type="button" value="Click me">
<script>
  elem.onclick = function() {
    alert('Thank you')
  }
</script>

要移除一个处理程序 —— 赋值 elem.onclick = null

问题

因为只有一个 onclick 属性,所以无法分配更多事件处理程序:

<input type="button" id="elem" onclick="alert('Before')" value="Click me">
<script>
  elem.onclick = function() { // 覆盖了现有的处理程序
    alert('After')
  }
</script>

处理程序只会在冒泡(+ 目标)阶段执行

注意

this

处理程序中的 this 指向对应的元素,就是处理程序所在的那个元素

函数是否需要加括号

attribute 需要加:

<input value="Click me" onclick="handleClick()" type="button">
<script>
function handleClick() {
  alert('Click!')
}
</script>

当浏览器读取 HTML 特性(attribute)时,浏览器将会使用特性中的内容创建一个处理程序:

button.onclick = function() {
  handleClick()

  // 不加括号的话就变成了
  handleClick
  // 函数不会调用
}

property 不加:

<input value="Click me" type="button">
<script>
function handleClick() {
  alert('Click!')
}

button.onclick = handleClick

// 加括号的话就变成了
button.onclick = handleClick()
// 实际上获得的是函数执行的结果,即 undefined(因为这个函数没有返回值),此代码不会工作
</script>

不要使用 setAttribute

这样的调用会失效:

// 点击 <body> 将产生 error,
// 因为特性总是字符串的,函数变成了一个字符串
document.body.setAttribute('onclick', function() { alert(1) })

大小写敏感

addEventListener

DOM Level 3 事件

addEventListener() 的工作原理是将函数或对象添加到调用它的 EventTarget 上的指定事件类型的事件侦听器列表中

对于某些事件,只能通过 addEventListener 设置处理程序,如:

// 永远不会运行
document.onDOMContentLoaded = function() {
  alert("DOM built")
}

// 正确方式
document.addEventListener("DOMContentLoaded", function() {
  alert("DOM built")
});

优点

事件对象

当事件发生时,会创建一个 event 对象,将详细信息放入其中(如:鼠标指针坐标、键盘按键信息),并将其作为参数传递给处理程序:

<input type="button" onclick="alert(event.type)" value="Event type">

<input type="button" onclick="foo(event)" value="Event type">

<script>
foo(e) {}

elem.onclick = function(e) {}

elem.addEventListener('click', function(e) {})

elem.addEventListener('click', { handleEvent(e) {} })
</script>

event 对象的一些属性:

事件传播流程

DOM Level 3 事件

![[Pasted image 20240429151024.png|500]]

  1. 捕获阶段:事件发生时,从 window 开始,自顶向下地捕获事件发生的目标元素
  2. 目标阶段:事件到达目标元素
  3. 冒泡阶段:目标元素自底向上地冒泡事件

停止传播

尽量不要使用停止传播

有时停止传播会产生隐藏的陷阱,产生“死区”

通常,没有真正的必要去阻止冒泡。一项看似需要阻止冒泡的任务,可以通过其他方法解决:

事件委托

如果有许多以类似方式处理的元素,就不必为每个元素分配一个处理程序,而是将单个处理程序放在它们的共同祖先上

在处理程序中,通过 event.target 以查看事件实际发生的位置并进行处理

好处:

局限性:

浏览器默认行为

许多事件会自动触发浏览器执行某些行为。例如:

阻止浏览器默认行为

使用 JS 处理一个事件,通常不希望发生相应的浏览器行为,而是想要实现其他行为进行替代。

有两种方式来告诉浏览器不希望它执行默认行为:

<a href="https://www.baidu.com/" onclick="event.preventDefault()">此时点击不会跳转</a>

<a href="https://www.baidu.com/" onclick="return false">此时点击不会跳转</a>

<a href="https://www.baidu.com/" id="linkDemo">此时点击不会跳转</a>
<script>
linkDemo.onclick = function() { return false }
</script>

但是注意:

<a href="https://www.baidu.com/" onclick="demo()">没有阻止浏览器默认行为</a>

<script>
function demo() { return false }
</script>

这样并没有阻止浏览器默认行为,因为上面代码相当于:

function demo() { return false }
a.onclick = function() { demo() }

需要这样写:

<a href="https://www.baidu.com/" onclick="return demo()">此时点击不会跳转</a>

<script>
function demo() { return false }
</script>
后续事件

某些事件会相互转化。如果阻止了第一个事件,那就没有第二个事件了

如:阻止了鼠标按下(mousedown)的浏览器的默认事件,会导致 input 不能获取到焦点(focus

这是因为浏览器行为在 mousedown 上被取消。如果用另一种方式进行输入,则仍然可以进行聚焦。如使用 Tab 键从第一个输入切换到第二个输入,但鼠标点击则不行。

不可取消事件

对于不可取消的事件(如:通过 EventTarget.dispatchEvent() 分派的且没有指定 cancelable: true 的事件),调用 preventDefault() 将没有任何效果

可以使用 event.cancelable 来检查该事件是否支持取消

处理程序选项:passive

addEventListener 的可选项 passive: true 向浏览器发出信号,表明处理程序将不会调用 preventDefault()

为什么需要这样做?

移动设备上会发生一些事件,例如 touchmove(当用户在屏幕上移动手指时),默认情况下会导致滚动,但是可以使用处理程序的 preventDefault() 来阻止滚动

因此,当浏览器检测到此类事件时,它必须首先处理所有处理程序,然后如果没有任何地方调用 preventDefault,则页面可以继续滚动。但这可能会导致 UI 中不必要的延迟和“抖动”。

passive: true 选项告诉浏览器,处理程序不会取消滚动。然后浏览器立即滚动页面以提供最大程度的流畅体验,并通过某种方式处理事件

对于某些浏览器(Firefox,Chrome),默认情况下,touchstart 和 touchmove 事件的 passive 为 true

题目

一个历史页面,上面有若干按钮的点击逻辑,每个按钮都有自己的 click 事件

新需求来了:给用户信息添加了一个属性 banned = true,此用户点击页面上的任何按钮或者元素,都不可响应原来的函数。而是直接 alert 提示:你被封禁了。