javascript-事件events

javascript-事件 events

浏览器中的 JavaScript 使用事件驱动的编程模型。 一切都始于事件。 本节介绍 JavaScript 事件以及事件处理的工作原理。

浏览器中的 JavaScript 使用事件驱动的编程模型

一切都始于事件。

事件可能是 DOM 已加载,或者是异步请求完成,或用户单击元素或滚动页面,或用户按下键盘。

有很多不同类型的事件。

事件处理器(Event handlers)

你可以使用事件处理程序响应任何事件,事件处理程序只是在事件发生时调用的函数

你可以为同一事件注册多个处理程序,并在事件发生时调用它们。

JavaScript 提供了三种注册事件处理程序的方法:

内联事件处理程序 inline

由于他自身的限制,这种类型的事件处理程序今天很少使用,但这是 JavaScript 早期的唯一方法

HTML 代码:

1
2
3
<a href='site.com' onclick='dosomething();'>
A link
</a>

DOM 事件处理器

一个对象只有一个事件处理器时这种方法很常用,因为在这种情况下无法添加多个处理程序

JavaScript 代码:

1
2
3
window.onload = () => {
//window loaded
};

它在处理XHR请求时最常用:

JavaScript 代码:

1
2
3
4
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
//.. do something
};

你可以使用 if ('onsomething' in window) {} 检查是否已将处理程序分配给某个属性。

使用 addEventListener()

这是 现代 方式。这种方法允许我们根据需求注册多个处理程序,你会发现它是绑定处理程序最受欢迎的方式:

JavaScript 代码:

1
2
3
window.addEventListener('load', () => {
//window loaded
});

注意:IE8 及以下版本不支持这个方法,可以使用 attachEvent() 代替。如果你需要支持旧浏览器,请记住这一点。

监听不同的元素

你可以监听 window 来拦截“全局”事件,比如键盘的使用,你也可以监听特定元素上发生的事件,比如鼠标点击了某个按钮。

这也是为什么 addEventListener 有时候在 window 上调用,有时间在某个 DOM 元素上

Event 对象

事件处理器会获得一个 Event 对象作为第一个参数:

JavaScript 代码:

1
2
3
4
const link = document.getElementById('my-link');
link.addEventListener('click', (event) => {
// link clicked
});

这个对象包含很多有用的属性和方法,比如:

  • target,事件发生的目标 DOM 元素
  • type,事件类型
  • stopPropagation(),调用以阻止 DOM 事件传播

(查看完整清单)Event

其它属性提供给特定的事件Event 只是不同事件的一个接口:

…等等
上面的每一个都链接到了 MDN 页面,你可以在那查看它们所有的属性。

例如,当一个键盘事件发生时,你可以检查哪个键被按下,通过 key 属性值得到一个可读格式的值( Escape, Enter 等等):

JavaScript 代码:

1
2
3
4
window.addEventListener('keydown', (event) => {
// key pressed
console.log(event.key);
});

鼠标事件中,我们可以检查按下了哪个鼠标按钮

JavaScript 代码:

1
2
3
4
5
const link = document.getElementById('my-link');
link.addEventListener('mousedown', (event) => {
// mouse button pressed
console.log(event.button); //0=left, 2=right
});

事件冒泡和事件捕捉

事件冒泡事件捕捉是事件传播的两个模型。

假设你的 DOM 结构是这样的:

HTML 代码:

1
2
3
<div id="container">
<button>Click me</button>
</div>

你希望跟踪用户何时单击该按钮,并且你有两个事件侦听器,一个在 button 上,另一个在 #container 上。 请记住,单击子元素将始终传播到其父元素,除非你停止事件传播(我们稍后会看到)。

这些事件侦听器会按照顺序调用,这个顺序通过事件冒泡/事件捕捉模型决定。

冒泡 意味着事件从被点击的元素(子元素)一直向上传播到所有祖先元素,从最近的一个开始。从子开始处理相应程序

在我们的例子中,点击 button 的话, button 上的处理器会在 #container 之前发生。

捕捉 恰恰相反:最外部的事件会在特定处理器之前发生,比如还是点击 button , 但先冒泡到最外层的 #container , #container上的事件处理先相应, 然后是 button 上的事件处理程序响应.

默认采用事件冒泡模型。从子开始处理

你也可以选择使用事件捕捉,通过将 addEventListener 的第三个参数设为 true:

JavaScript 代码:

1
2
3
4
5
6
7
document.getElementById('container').addEventListener(
'click',
() => {
//window loaded
},
true
);

注意:首先运行所有捕获事件处理程序
然后是所有冒泡的事件处理程序

这个顺序遵循这个原则DOM 遍历从 Window 对象开始的所有元素,直到找到被点击的元素项。执行此操作时,调用与事件关联的任何事件处理程序(捕获阶段)。

一旦找到目标元素,它会重复这个过程直到回到 Window 对象,此时调用相应的事件处理器(冒泡阶段)。

这样图可以帮助你理解这个过程:

注意咯, 如果你设置了多个事件处理, 比如一个 window 上设置了 2 个 一个用 冒泡, 一个用 捕获, 那么 都会触发的, 然后触发顺序是 下面的 1 2 3 4 先 处理 捕获的 在处理 冒泡 的事件处理程序

bubble.png

停止传播

DOM 元素上的事件将传播到其所有父元素上,除非手动停止传播:

HTML 代码:

1
2
3
4
5
6
7
<html>
<body>
<section>
<a id="my-link" ...></a>
</section>
</body>
</html>

a 上的 click 事件会传播到 section 然后是 body

你可以调用 EventstopPropagation() 方法来停止事件传播,通常放在事件处理程序的末尾(注:我个人喜好放在事件处理程序的开始处):

JavaScript 代码:

1
2
3
4
5
6
7
const link = document.getElementById('my-link');
link.addEventListener('mousedown', (event) => {
// process the event
// ...

event.stopPropagation();
});

常见事件

以下可能是你会处理的最常见事件的列表。

load

页面加载完成后,在 windowbody 元素上触发 load 事件。

鼠标事件

单击鼠标按钮时 click 事件触发。 单击鼠标两次时触发 dbclick 事件。 当然,在这种情况下,click 事件会在此事件之前触发。mousedownmousemovemouseup 可以和拖动事件结合在一起。小心使用 mousemove因为它会在鼠标移动过程中触发很多次(稍后会看到节流)

键盘事件

当按下键盘键时 keydown 事件就会触发(当按下按钮时,任何时候重复键)。当键被释放时,将触发 keyup 事件。

滚动(Scroll)

每次滚动页面时都会在 window 上触发 scroll 事件。在事件处理程序中,你可以通过检查 window.scrollY(Y 轴)(注:我个人喜好用 document.documentElement.scrollTop) 来检查当前的滚动位置。

请记住,此事件不是一次性的事件。它在滚动期间会发生很多次,而不仅仅是在滚动的结尾或开始时,所以不要在处理程序中进行任何频繁的计算或操作 – 而是使用节流代替。

节流(Throttling)

如上所述,mousemovescroll 这两个事件都不是一次性事件,而是在持续操作的时间内连续调用它们的事件处理函数。

这是因为它们提供坐标,因此你可以跟踪正在发生的事件。

如果你在这些事件处理器中进行复杂的操作,则会影响性能并导致站点用户体验不佳。

Lodash throttle 这样的库提供了 100 行代码实现的节流函数来处理这个问题。一个简单易懂的实现是使用 setTimeout 每隔 100ms 缓存一次滚动事件:

JavaScript 代码:

1
2
3
4
5
6
7
8
9
10
let cached = null;
window.addEventListener('scroll', (event) => {
if (!cached) {
setTimeout(() => {
//you can access the original event at `cached`
cached = null;
}, 100);
}
cached = event;
});

防抖动 debounce

两者区别

理解 Debouncing 与 Throttling 的区别 666
白话 debounce 和 throttle

debounce 的主要区别是 推迟这次调用。 电梯例子
throttle 保证方法每 Xms 有规律的执行,

  • debounce:把触发非常频繁的事件合并成一次执行
  • throttle:设置一个阀值,在阀值内,把触发的事件合并成一次执行;当到达阀值,必定执行一次事件

了解了 throttle 和 debounce 之后,下面看看他们的常用场景:

debounce

  • 对于键盘事件,当用户输入比较频繁的时候,可以通过 debounce 合并键盘事件处理
  • 对于 ajax 请求的情况,例如当页面下拉超过一定返回就通过 ajax 请求新的页面内容,这时候可以通过 debounce 合并 ajax 请求事件

throttle

  • 对于键盘事件,当用户输入非常频繁,但是我们又必须要在一定时间内(阀值)内执行处理函数的时候,就可以使用 throttle
    • 例如,一些网页游戏的键盘事件
  • 对于鼠标移动和窗口滚动,鼠标的移动和窗口的滚动会带来大量的事件,但是在一段时间内又必须看到页面的效果
    • 例如对于可以拖动的 div,如果使用 debounce,那么 div 会在拖动停止后一下子跳到目标位置;这时就需要使用 throttle

参考

JavaScript 中的事件(Events) – JavaScript 完全手册(2018 版)