- 前言
- 关于 ECMASCRIPT 发展史和现状
- ES6 带来的重大特性
- ES2016(ES7)的改进
- ES2017(ES8)带来的重大新特性
- ES2018(ES9)带来的重大新特性
- JavaScript 编码风格指南
- JavaScript 词法结构(构建块)
- JavaScript 变量
- JavaScript 数据类型
- JavaScript 表达式
- 原型继承
- 如何使用 JavaScript 中的 Classes(类)
- JavaScript 异常处理
- JavaScript 中的分号(;)
- JavaScript 中的引号
- JavaScript 字面量模板(Template Literals)指南
- JavaScript 中的 function(函数)
- JavaScript 箭头函数(Arrow Function)
- JavaScript 中的闭包(Closures)
- JavaScript 数组(Arrays)
- JavaScript 中的循环(Loops)
- JavaScript 中的事件(Events)
- JavaScript 中的事件循环(Event Loop)
- JavaScript 异步编程和回调
- 理解 JavaScript 中的 Promises
- 用 async 和 await 编写现代 JavaScript 异步代码
- JavaScript 中的 循环(Loops) 和 作用域(Scope)
- JavaScript 定时器 setTimeout() 和 setInterval()
- JavaScript 中的 this
- JavaScript 严格模式(Strict Mode)
- JavaScript 中的 立即执行函数表达式(IIFE)
- JavaScript 中的数学运算符
- JavaScript 中的 Math 对象
- 介绍 ES Modules(模块)
- 介绍 CommonJS
- JavaScript 术语表
JavaScript 中的事件(Events)
浏览器中的 JavaScript 使用事件驱动的编程模型。 一切都始于事件。 本节介绍 JavaScript 事件以及事件处理的工作原理。
浏览器中的 JavaScript 使用事件驱动的编程模型。
一切都始于事件。
事件可能是 DOM 已加载,或者是异步请求完成,或用户单击元素或滚动页面,或用户安心键盘。
有很多不同类型的事件。
事件处理器(Event handlers)
你可以使用事件处理程序响应任何事件,事件处理程序只是在事件发生时调用的函数。
你可以为同一事件注册多个处理程序,并在事件发生时调用它们。
JavaScript 提供了三种注册事件处理程序的方法:
内联事件处理程序
由于他自身的限制,这种类型的事件处理程序今天很少使用,但这是 JavaScript 早期的唯一方法:
<a href="site.com" onclick="dosomething();">A link</a>
DOM 事件处理器
当一个对象只有一个事件处理器时这种方法很常用,因为在这种情况下无法添加多个处理程序:
window.onload = () => { //window loaded }
它在处理 XHR 请求时最常用:
const xhr = new XMLHttpRequest() xhr.onreadystatechange = () => { //.. do something }
你可以使用 if ('onsomething' in window) {}
检查是否已将处理程序分配给某个属性。
使用 addEventListener()
这是 现代 方式。这种方法允许我们根据需求注册多个处理程序,你会发现它是绑定处理程序最受欢迎的方式:
window.addEventListener('load', () => { //window loaded })
注意:IE8 及以下版本不支持这个方法,可以使用
attachEvent()
代替。如果你需要支持旧浏览器,请记住这一点。
监听不同的元素
你可以监听 window
来拦截“全局”事件,比如键盘的使用,你也可以监听特定元素上发生的事件,比如鼠标点击了某个按钮。
这也是为什么 addEventListener
有时候在 window
上调用,有时间在某个 DOM 元素上。
Event 对象
事件处理器会获得一个 Event
对象作为第一个参数:
const link = document.getElementById('my-link') link.addEventListener('click', event => { // link clicked })
这个对象包含很多有用的属性和方法,比如:
target
,事件发生的目标 DOM 元素type
,事件类型stopPropagation()
,调用以阻止 DOM 事件传播
( 查看完整清单 )
其它属性提供给特定的事件,Event 只是不同事件的一个接口:
上面的每一个都链接到了 MDN 页面,你可以在那查看它们所有的属性。
例如,当一个键盘事件发生时,你可以检查哪个键被按下,通过 key 属性值得到一个可读格式的值( Escape
, Enter
等等):
window.addEventListener('keydown', event => { // key pressed console.log(event.key) })
在鼠标事件中,我们可以检查按下了哪个鼠标按钮:
const link = document.getElementById('my-link') link.addEventListener('mousedown', event => { // mouse button pressed console.log(event.button) //0=left, 2=right })
事件冒泡和事件捕捉
事件冒泡和事件捕捉是事件传播的两个模型。
假设你的 DOM 结构是这样的:
<div id="container"> <button>Click me</button> </div>
你希望跟踪用户何时单击该按钮,并且你有两个事件侦听器,一个在 button
上,另一个在 #container
上。 请记住,单击子元素将始终传播到其父元素,除非你停止事件传播(我们稍后会看到)。
这些事件侦听器会按照顺序调用,这个顺序通过事件冒泡/事件捕捉模型决定。
冒泡 意味着事件从被点击的元素(子元素)一直向上传播到所有祖先元素,从最近的一个开始。
在我们的例子中, button
上的处理器会在 #container
之前发生。
捕捉 恰恰相反:最外部的事件会在特定处理器之前发生,比如 button
的处理程序。。
默认采用事件冒泡模型。
你也可以选择使用事件捕捉,通过将 addEventListener 的第三个参数设为 true
:
document.getElementById('container').addEventListener( 'click', () => { //window loaded }, true )
注意: 首先运行所有捕获事件处理程序。
然后是所有冒泡的事件处理程序。
这个顺序遵循这个原则:DOM 遍历从 Window 对象开始的所有元素,直到找到被点击的元素项。执行此操作时,调用与事件关联的任何事件处理程序(捕获阶段)。
一旦找到目标元素,它会重复这个过程直到回到 Window 对象,此时调用相应的事件处理器(冒泡阶段)。
这样图可以帮助你理解这个过程:
停止传播
DOM 元素上的事件将传播到其所有父元素上,除非手动停止传播:
<html> <body> <section> <a id="my-link" ...>
a
上的 click 事件会传播到 section
然后是 body
。
你可以调用 Event
的 stopPropagation()
方法来停止事件传播,通常放在事件处理程序的末尾(注:我个人喜好放在事件处理程序的开始处):
const link = document.getElementById('my-link') link.addEventListener('mousedown', event => { // process the event // ... event.stopPropagation() })
常见事件
以下可能是你会处理的最常见事件的列表。
load
页面加载完成后,在 window
和 body
元素上触发 load
事件。
鼠标事件
单击鼠标按钮时 click
事件触发。 单击鼠标两次时触发 dbclick
事件。 当然,在这种情况下, click
事件会在此事件之前触发。 mousedown
, mousemove
和 mouseup
可以和拖动事件结合在一起。小心使用 mousemove
,因为它会在鼠标移动过程中触发很多次(稍后会看到节流)。
键盘事件
当按下键盘键时 keydown
事件就会触发(当按下按钮时,任何时候重复键)。当键被释放时,将触发 keyup
事件。
滚动(Scroll)
每次滚动页面时都会在 window
上触发 scroll
事件。在事件处理程序中,你可以通过检查 window.scrollY
(Y 轴)(注:我个人喜好用 document.documentElement.scrollTop
) 来检查当前的滚动位置。
请记住,此事件不是一次性的事件。它在滚动期间会发生很多次,而不仅仅是在滚动的结尾或开始时,所以不要在处理程序中进行任何频繁的计算或操作 – 而是使用节流代替。
节流(Throttling)
如上所述, mousemove
和 scroll
这两个事件都不是一次性事件,而是在持续操作的时间内连续调用它们的事件处理函数。
这是因为它们提供坐标,因此你可以跟踪正在发生的事件。
如果你在这些事件处理器中进行复杂的操作,则会影响性能并导致站点用户体验不佳。
像 Lodash 这样的库提供了 100 行代码实现的节流函数来处理这个问题。一个简单易懂的实现是使用 setTimeout
每隔 100ms 缓存一次滚动事件:
let cached = null window.addEventListener('scroll', event => { if (!cached) { setTimeout(() => { //you can access the original event at `cached` cached = null }, 100) } cached = event })
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论