JS知识点梳理(9)-事件

JavaScript 与 HTML 之间的交互是通过事件实现的。事件就是文档或浏览器窗口中发生的一些特定的交互瞬间,并使用侦听器(或处理程序)来预定事件,以便在事件发生时执行相应的代码。

事件流

事件流描述的是从页面中接受事件的顺序,有 3 中类型

  • 事件冒泡:事件开始时由最具体的元素接受,然后沿着 DOM 树逐级向上传播,直到传播到 document 对象
  • 事件捕获:不太具体的节点更早接受到事件,最具体的节点最后接受到事件,即在事件到达预定目标之前捕获它。
  • DOM 事件流:分 3 个阶段依次为,事件捕获阶段,处于目标阶段,事件冒泡阶段。实际的目标在捕获阶段不会接受到事件。

事件处理程序

事件时用户或浏览器自身执行的某种动作,而响应某个事件的函数叫做事件处理程序/事件侦听器。为事件指定处理程序的方法(3 种)

HTML 事件处理程序

    1. 直接在 HTML 中定义事件处理程序及要执行的具体动作
      1
      <input type="button" value="111" onclick="alert('111')" />
    1. 在 HTML 中定义事件处理程序,执行的动作则调用其他地方定义的脚本
      1
      2
      3
      4
      5
      6
      7
      <input type="button" value="222" onclick="showMsg(event)" />
      <script>
      function showMsg(event) {
      console.log("event", event);
      alert(222);
      }
      </script>

注意

  • 事件处理程序在执行时,有权访问全局作用域中任何代码。
  • 事件处理程序会接受一个参数 event,即事件对象。

缺点

  • HTML 与 JavaScript 代码紧密耦合,若要更换事件处理程序,二者则都需要改动。
  • 存在一个时差问题,比如,用户已经在 HTML 页面中触发事件,但当时事件处理程序包含在一个外部文件,可能尚未加载。

DOM0 级事件处理程序

通过 Javascript 指定事件处理程序,就是将一个函数赋值给一个事件处理程序属性。每个元素都有自己的事件处理程序属性,这些属性通常全部小写,例如 onclick,将该属性的值设置为一个函数,就可指定事件处理程序

1
2
3
4
5
6
7
8
9
<input type="button" value="3333" id="btn3" />
<script>
btn3.onclick = function () {
alert(333);
};
btn3.onmouseover = function () {
console.log("mouseover");
};
</script>

注意

  • 指定事件处理程序在运行之前是无效的,因此如果这些代码在按钮之后,就有可能在一段时间内点击按钮没反应
  • 使用 DOM0 级方法指定的事件处理程序被认为是元素的方法,所有此时的事件处理程序是在元素的作用域中运行,级程序中的 this 执行当前元素
  • 重复指定事件处理程序,后者会覆盖前者。因此,仅触发一次执行动作
  • 删除 DOM0 级方法指定的事件处理程序,只需将事件处理程序属性设置为null即可。btn3.onclick = null;

DOM2 级事件处理程序

DOM2 级事件处理程序定义了 2 个方法:

  • addEventListener(type,listener,useCapture),添加事件处理程序
  • removeEventListener(type,listener,useCapture),删除事件处理程序

这 2 个方法都可接受 3 个参数:

    1. 要处理的事件名
    1. 作为事件处理程序的函数
    1. 表示事件流方式的布尔值。true 为捕获阶段处理程序,false 为冒泡阶段处理程序。默认为 false
1
2
3
4
5
6
7
8
9
10
11
12
13
14

var btn4 = document.getElementById("btn4");
var handler = function (event) {
console.log(event, this);
alert(4444);
};
btn4.addEventListener("click", handler, false);
btn4.addEventListener(
"click",
function () {
alert(555);
},
false
);

注意

  • 与 DOM0 级方法一样,这里添加的事件处理程序也是在其依附的元素的作用域中运行,即 this 执行当前元素
  • 可以添加多个事件处理程序,并按照添加顺序触发
  • 通过addEventListener()添加的事件处理程序只能使用removeEventListener()来移除,且移除是传入的参数与添加时使用的参数相同。这也就意味着,通过addEventListener()添加的匿名函数将无法移除

事件对象

在触发 DOM 上的事件时,都会产生一个事件对象event,无论指定事件处理程序使用什么方法,都会将 event 对象传入到事件处理程序中。event 对象包含与创建它的特定事件有关的属性和方法,以下成员是所有事件都具有的:

属性/方法 类型 说明
type String 被触发事件的类型
bubbles Boolean 是否冒泡
cancelable Boolean 是否可取消事件的默认行为
currentTarget Element 事件处理程序当前正在处理事件的那个元素
target Element 事件的目标元素(真正触发事件的元素)
defaultPrevented Boolean 是否已经调用 preventDefault()
preventDefault() Function 取消事件的默认行为
stopPropagation() Function 取消事件的传播,即取消事件的进一步捕获或冒泡
  • 在事件处理程序内部,this 对象始终等于currentTarget的值,而target则只包含事件的实际目标。
  • 只有在事件处理程序执行期间,event 对象才存在,一旦事件处理程序执行完毕,event 对象会被销毁

事件类型

  • UI 事件(UIEvent):当用户与页面上的元素交互时触发
  • 焦点事件(FocusEvent):当元素获得或失去焦点时触发
  • 鼠标事件(MouseEvent):用户通过鼠标在页面上执行操作时触发
  • 滚动事件(WheelEvent):使用鼠标滚动时触发
  • 文本事件(InputEvent):可编辑的内容被修改时触发
  • 键盘事件(KeyboardEvent):当用户通过键盘在页面上执行操作时触发
  • HTML5 事件

UI 事件

  • load: 当页面完全加载后在 window 上触发;当所有框架都加载完毕时在框架集上触发;当图集加载完毕时在<img>元素上触发;当嵌入的内容加载完毕时在<object>元素上触发
  • unload: 当页面完全卸载后在 window 上触发;当所有框架都卸载完成时在框架集上触发;当嵌入内容卸载完成时在<object>元素上触发
  • abort: 在用户停止下载过程时,如果嵌入的内容没有加载完,则在<object>元素上触发
  • error: 当发生 javascript 错误时在 window 上触发;当无法加载图片时在<img>元素上触发;当无法加载嵌入内容时在<object>元素上触发
  • select: 当文本内容被选中时触发,一般在<input><textarea>元素
  • resize: 当窗口大小变化时在 window 上触发
  • scroll: 当用户滚动带滚动条的元素内容时,在该元素上触发

焦点事件

当焦点从一个元素移动到另一个元素,会依次触发以下事件:

    1. focusout,在失去焦点的元素(失去焦点之前)触发
    1. focusint, 在获得焦点的元素(获得焦点之前)触发
    1. blur, 在失去焦点的元素(失去焦点之后)触发
    1. focus, 在获得焦点的元素(获得焦点之后)触发

鼠标事件

9 个鼠标事件,页面上所有元素都支持鼠标事件,除了mouseenter,mouseleave,所有鼠标事件都会冒泡,也可以被取消

  • click:单击鼠标(一般是左键),或者按下回车键时触发
  • dblclick:双击鼠标触发
  • mousedown:按下任意鼠标按钮触发,不能通过键盘触发
  • mouseenter:光标从元素外部首次移动到元素范围之内触发。此事件不冒泡,而且在光标移动到后代元素也不会触发
  • mouseleave:光标从元素之上移动到元素之外时触发。此事件不冒泡,移动到后代元素不触发
  • mousemove: 光标在元素内部移动时触发
  • mouseout: 光标从一个元素之上,移入到另一个元素时触发。
  • mouseover: 光标从一个元素外部,移入另一个元素边界之内触发
  • mouseup: 释放鼠标按钮时触发

事件触发顺序

  • 只有在同一个元素相继触发 mousedown,mouseup 事件,才会触发 click。如果 mousedown,mosueup 中的任意一个被取消,就不会触发 click 事件
  • 只有触发两次 click,才会触发一次 dblclick 事件
双击鼠标 事件触发顺序
  1. mousedown
  2. mousemove
  3. mouseup
  4. click
  5. mousemove
  6. mousedown
  7. mousemove
  8. mouseup
  9. click
  10. dblclick
光标由元素 B 移入到元素 A,再移出
事件类型 所在元素 说明
1 mousemove B
光标移入 元素 A
2 mouseover A
3 mouseenter A
4 mousemove A 多次触发 mousemove 事件
光标移出 元素 A
5 mouseout A
6 mouseleave A
光标移入元素 A,然后移动到一个嵌套的元素 B,再返回元素 A,再移出元素 A

| 事件类型 | 所在元素 | 说明 |
| ————- | ——– | ————————– | — |
| 1 mousemove | | |
| | | 光标移入元素 A |
| 2 mouseover | A | |
| 3 mouseenter | A | |
| 4 mousemove | A | 多次触发 mousemove 事件 |
| | | 光标移入到嵌套的元素 B |
| 5 mouseout | A | |
| 6 mouseover | B | |
| 7 mouseenter | B | |
| 8 mousemove | B | 多次触发 mousemove 事件 |
| | | 光标 从元素 B 移入到元素 A |
| 9 mouseout | B | |
| 10 mouseleave | B | |
| 11 mouseover | A | |
| 12 mousemove | A | 多次触发 mousemove 事件 |
| | | 光标从 A 元素移出 |
| 13 mouseout | A | | |
| 14 mouseleave | A | |

    1. mousemove

光标坐标位置

  • 视口坐标位置,event.clientX,event.clientY
  • 页面坐标位置,event.pageX,event.pageY
  • 屏幕坐标位置.event.screenX,event.screenY

修改键

DOM 规定了 4 个属性,表示这些修改键的状态:shiftKey,altKey,ctrlKey,metaKey,相应的键被按下,其值为 true,否则为 false。当鼠标事件发生时,就可以检测这几个属性就可以确定用户是否同时按下了其中的几个键。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
btn4.addEventListener("click", function (event) {
var keys = [];
console.log(event);
if (event.shiftKey) {
keys.push("shift");
}
if (event.ctrlKey) {
keys.push("ctrl");
}
if (event.metaKey) {
keys.push("meta");
}
if (event.altKey) {
keys.push("alt");
}
alert(`keys: ${keys.join(", ")}`);
});

滚动事件

当用户通过鼠标滚动页面时,就会触发 mousewheel 事件,这个事件可以在任何元素上触发,但最终都会冒泡到 document 或 window 对象上。

与 mousewheel 事件对象的 event 对象除了包含鼠标事件的所有标准信息外,还包含一个特性的wheelDelta属性,当向上滚动时,wheelDelta的属性的值是 120 倍数,向下滚动时,wheelDelta的属性的值是-120 倍数

1
2
3
4
5
6
7
8
9
10
<div style="height: 2500px;">
<span id="wheelDelta" style="position: fixed; top: 90px;">0</span>
</div>

<script>
document.addEventListener("mousewheel", function (event) {
console.log(event);
document.getElementById("wheelDelta").innerHTML = event.wheelDelta;
});
</script>

文本事件

  • beforeinput,可编辑的内容被修改之前触发
  • input,可编辑的内容被修改之后触发

事件对象的data属性的值为输入文本的内容

键盘事件

  • keydown:按下键盘上的任意键触发,按住不放,会重复触发
  • keyup:释放键盘上的按键时触发
  • keypress,已废弃

键盘事件 触发顺序

  1. keydown
  2. beforeinput
  3. input
  4. keyup
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<input type="text" name="" id="input" />
<script>
var input = document.getElementById("input");
input.addEventListener("beforeinput", function (event) {
console.log("beforeinput>>>", event);
});
input.addEventListener("input", function (event) {
console.log("input>>>", event);
});
input.addEventListener("keydown", function (event) {
console.log("keydown>>", event);
});
input.addEventListener("keyup", function (event) {
console.log("keyup>>>", event);
});
</script>

HTML5 事件

  • contextmenu 事件(已过时,被删除)

    由于 contextmenu 事件是冒泡的,因此可以为 document 指定一个事件处理程序,用以处理页面中所有的此类事件。

  • DOMContentLoaded 事件
    在形成完整的 DOM 树之后就会触发,不理会图像、js 文件、css 文件或其他资源是否已经下载完毕。

事件相关的性能

事件委托

事件委托利用了事件冒泡,只指定一个事件处理程序,就可以关联某一类的所有事件。例如,click 事件会一种冒泡到 document 层,所以只需为整个页面指定一个 onclick 事件处理程序,而不必给每个可单击的元素分别添加事件处理程序。与传统做法相比具有的优势:

  • document 对象很快就可以访问,而且可以在页面生命周期的任何点上为它添加事件处理程序,无需等待 DOMContentLoaded 或 load 事件
  • 只添加一个事件处理程序所需的 DOM 引用更少,所花费是时间也更少,页面加载更快
  • 这个页面占用的内存空间更少

移除不需要的事件处理程序

在不需要的时候要移除事件处理程序,因为内存中留有的那些过时不用的“空事件处理程序”也是造成 web 内存与性能问题的原因。
例如,通过innerHTML替换元素时,如果带有事件处理程序的元素被innerHTML替换,那么用来添加到元素的事件处理程序极有可能无法被当作垃圾回收。
所以,在已经知道要移除 dom 时,最后手动移除对应的事件处理程序

模拟事件

除了用户操作或通过浏览器功能触发事件,也可以使用 JavaScript 在任意时刻刻意触发特定的事件。在测试 web 应用程序中,模拟触发事件是极其有用的技术。