"红宝书" 通常指的是《JavaScript 高级程序设计》,这是一本由 Nicholas C. Zakas(尼古拉斯·扎卡斯)编写的 JavaScript 书籍,是一本广受欢迎的经典之作。这本书是一部翔实的工具书,满满的都是 JavaScript 知识和实用技术。
不管你有没有刷过红宝书,如果现在还没掌握好,那就一起来刷红宝书吧,go!go!go!
系列文章:
第一部分:基本知识(重点、反复阅读)
第二部分:进阶内容(重点、反复阅读)
- 前端必刷系列之红宝书------第 7 章
- 前端必刷系列之红宝书------第 8 章
- 前端必刷系列之红宝书------第 9 章
- 前端必刷系列之红宝书------第 10 章
- 前端必刷系列之红宝书------第 11 章
第三部分:BOM 和 DOM (着重学习)
第 17 章 事件
事件流
事件流
描述了页面接收事件的顺序。结果非常有意思,IE 和 Netscape 开发团队提出了几乎完全相反的事件流方案。IE 将支持事件冒泡流,而 Netscape Communicator 将支持事件捕获流
。
IE 事件流被称为事件冒泡
,这是因为事件被定义为从最具体的元素(文档树中最深的节点)开始触发,然后向上传播至没有那么具体的元素(文档)。
所有现代浏览器都支持事件冒泡,只是在实现方式上会有一些变化。现代浏览器中的事件会一直冒泡到 window 对象。
事件捕获
的意思是最不具体的节点应该最先收到事件,而最具体的节点应该最后收到事件。事件捕获实际上是为了在事件到达最终目标前拦截事件。
事件捕获
得到了所有现代浏览器的支持。实际上,所有浏览器都是从 window 对象开始捕获事件,而 DOM2 Events规范规定的是从 document 开始。
DOM2 Events 规范规定事件流分为 3 个阶段:事件捕获、到达目标和事件冒泡
。事件捕获最先发生,为提前拦截事件提供了可能。然后,实际的目标元素接收到事件。最后一个阶段是冒泡,最迟要在这个阶段响应事件。
事件处理程序
为响应事件而调用的函数被称为事件处理程序
(或事件监听器)。事件处理程序的名字以"on"开头,因此 click 事件的处理程序叫作 onclick,而 load 事件的处理程序叫作 onload。有很多方式可以指定事件处理程序。
js
let btn = document.getElementById("myBtn");
btn.onclick = function() {
console.log(this.id); // "myBtn"
};
// 在事件处理程序里通过 this 可以访问元素的任何属性和方法。
btn.onclick = null; // 移除事件处理程序
js
// 事件名、事件处理函数和一个布尔值,
// true 表示在捕获阶段调用事件处理程序,
// false(默认值)表示在冒泡阶段调用事件处理程序。
let btn = document.getElementById("myBtn");
let handler = function() {
console.log(this.id);
};
btn.addEventListener("click", handler, false);
// 其他代码
btn.removeEventListener("click", handler, false); // 有效果!
事件对象
在 DOM 中发生事件时,所有相关信息都会被收集并存储在一个名为 event 的对象中
。这个对象包 含了一些基本信息,比如导致事件的元素、发生的事件类型,以及可能与特定事件相关的任何其他数据。 所有浏览器都支持这个 event 对象
,尽管支持方式不同。
js
// preventDefault()方法用于阻止特定事件的默认动作。
// 比如,链接的默认行为就是在被单击时导航到 href 属性指定的 URL。
// 如果想阻止这个导航行为,可以在 onclick 事件处理程序中取消
let link = document.getElementById("myLink");
link.onclick = function(event) {
event.preventDefault();
};
js
// stopPropagation()方法用于立即阻止事件流在 DOM 结构中传播,取消后续的事件捕获或冒泡。
// 例如,直接添加到按钮的事件处理程序中调用 stopPropagation(),
// 可以阻止 document.body 上注册的事件处理程序执行。
let btn = document.getElementById("myBtn");
btn.onclick = function(event) {
console.log("Clicked");
event.stopPropagation();
};
document.body.onclick = function(event) {
console.log("Body clicked");
};
js
// eventPhase 属性可用于确定事件流当前所处的阶段。
// 如果在捕获阶段被调用,则eventPhase 等于 1;
// 如果在目标上被调用,则 eventPhase 等于 2;
// 如果在冒泡阶段被调用,则 eventPhase 等于 3。
let btn = document.getElementById("myBtn");
btn.onclick = function(event) {
console.log(event.eventPhase); // 2
};
document.body.addEventListener("click", (event) => {
console.log(event.eventPhase); // 1
}, true);
document.body.onclick = (event) => {
console.log(event.eventPhase); // 3
};
事件类型
DOM3 Events 定义了如下事件类型:
用户界面事件
(UIEvent):涉及与 BOM 交互的通用浏览器事件。焦点事件
(FocusEvent):在元素获得和失去焦点时触发。鼠标事件
(MouseEvent):使用鼠标在页面上执行某些操作时触发。滚轮事件
(WheelEvent):使用鼠标滚轮(或类似设备)时触发。输入事件
(InputEvent):向文档中输入文本时触发。键盘事件
(KeyboardEvent):使用键盘在页面上执行某些操作时触发。合成事件
(CompositionEvent):在使用某种 IME(Input Method Editor,输入法编辑器)输入 字符时触发。
用户界面事件
load
:在 window 上当页面加载完成后触发,在窗套(<frameset>
)上当所有窗格(<frame>
)都加载完成后触发,在<img>
元素上当图片加载完成后触发,在<object>
元素上当相应对象加载完成后触发。unload
:在 window 上当页面完全卸载后触发,在窗套上当所有窗格都卸载完成后触发,在<object>
元素上当相应对象卸载完成后触发。abort
:在<object>
元素上当相应对象加载完成前被用户提前终止下载时触发。error
:在 window 上当 JavaScript 报错时触发,在<img>
元素上当无法加载指定图片时触发,在<object>
元素上当无法加载相应对象时触发,在窗套上当一个或多个窗格无法完成加载时触发。select
:在文本框(<input>
或textarea
)上当用户选择了一个或多个字符时触发。resize
:在 window 或窗格上当窗口或窗格被缩放时触发。scroll
:当用户滚动包含滚动条的元素时在元素上触发。<body>
元素包含已加载页面的滚动条。
焦点事件
blur
:当元素失去焦点时触发。这个事件不冒泡,所有浏览器都支持。focus
:当元素获得焦点时触发。这个事件不冒泡,所有浏览器都支持。focusin
:当元素获得焦点时触发。这个事件是 focus 的冒泡版。focusout
:当元素失去焦点时触发。这个事件是 blur 的通用版。
鼠标和滚轮事件
-
click
:在用户单击鼠标主键(通常是左键)或按键盘回车键时触发。这主要是基于无障碍的考 虑,让键盘和鼠标都可以触发 onclick 事件处理程序。 -
dblclick
:在用户双击鼠标主键(通常是左键)时触发。 -
mousedown
:在用户按下任意鼠标键时触发。这个事件不能通过键盘触发。 -
mouseenter
:在用户把鼠标光标从元素外部移到元素内部时触发。这个事件不冒泡,也不会在 光标经过后代元素时触发。 -
mouseleave
:在用户把鼠标光标从元素内部移到元素外部时触发。这个事件不冒泡,也不会在 光标经过后代元素时触发。 -
mousemove
:在鼠标光标在元素上移动时反复触发。这个事件不能通过键盘触发。 -
mouseout
:在用户把鼠标光标从一个元素移到另一个元素上时触发。移到的元素可以是原始元 素的外部元素,也可以是原始元素的子元素。这个事件不能通过键盘触发。 -
mouseover
:在用户把鼠标光标从元素外部移到元素内部时触发。这个事件不能通过键盘触发。 -
mouseup
:在用户释放鼠标键时触发。这个事件不能通过键盘触发。 -
当鼠标滚轮
向前滚动
时,wheelDelta
每次都是+120
; -
而当鼠标滚轮
向后滚动
时,wheelDelta
每次都是--120
js
document.addEventListener("mousewheel", (event) => {
console.log(event.wheelDelta);
});
键盘与输入事件
-
keydown
,用户按下键盘上某个键时触发,而且持续按住会重复触发。 -
keyup
,用户释放键盘上某个键时触发。 -
keyCode 和 key:
keyCode
属性返回一个表示按下键的键码,而key
属性返回一个表示按下的是哪个键的字符串。注意,keyCode
属性已经逐渐被key
属性替代,因为key
提供了更多的信息。 -
shiftKey、ctrlKey、altKey、metaKey: 这些属性表示是否同时按下了 Shift、Ctrl、Alt 和 Meta(Command)键。
js
document.addEventListener('keydown', function(event) {
console.log('Key pressed:', event.key);
console.log('Key code:', event.keyCode);
console.log('Shift key pressed:', event.shiftKey);
console.log('Ctrl key pressed:', event.ctrlKey);
console.log('Alt key pressed:', event.altKey);
});
js
let textbox = document.getElementById("myText");
textbox.addEventListener("textInput", (event) => {
console.log(event.data);
console.log(event.inputMethod);
});
event.inputMethod值:
- 0,表示浏览器不能确定是什么输入手段;
- 1,表示键盘;
- 2,表示粘贴;
- 3,表示拖放操作;
- 4,表示 IME;
- 5,表示表单选项;
- 6,表示手写(如使用手写笔);
- 7,表示语音;
- 8,表示组合方式;
- 9,表示脚本。
合成事件
js
let textbox = document.getElementById("myText");
textbox.addEventListener("compositionstart", (event) => {
console.log(event.data);
});
textbox.addEventListener("compositionupdate", (event) => {
console.log(event.data);
});
textbox.addEventListener("compositionend", (event) => {
console.log(event.data);
});
HTML5 事件
contextmenu 事件 :是在用户右键点击页面元素时触发的事件。当用户右键点击页面的某个元素时,浏览器会触发 contextmenu
事件,通常会显示浏览器的上下文菜单(context menu)。开发者可以通过监听这个事件来执行特定的操作或者自定义右键菜单。
js
window.addEventListener("load", (event) => {
let div = document.getElementById("myDiv");
div.addEventListener("contextmenu", (event) => {
event.preventDefault();
let menu = document.getElementById("myMenu");
menu.style.left = event.clientX + "px";
menu.style.top = event.clientY + "px";
menu.style.visibility = "visible";
});
document.addEventListener("click", (event) => {
document.getElementById("myMenu").style.visibility = "hidden";
});
});
beforeunload 事件:是在用户准备离开页面之前触发的事件。这个事件通常用于提示用户保存未保存的修改或执行其他清理操作,因为用户关闭页面时可能会丢失一些数据。
当用户关闭标签、关闭浏览器、输入新的 URL 或进行其他导致页面卸载的操作时,beforeunload
事件会被触发。在这个事件中,你可以显示一个确认消息,询问用户是否确定要离开页面。
js
window.addEventListener("beforeunload", (event) => {
let message = "I'm really going to miss you if you go.";
event.returnValue = message;
return message;
});
DOMContentLoaded 事件:是在 HTML 文档解析完成并且所有的 DOM 树构建完成后触发的事件。这一时刻是在浏览器加载页面的过程中相对较早的阶段。
DOMContentLoaded
不等待样式表、图像以及其他资源的加载完成,而是在 DOM 可以被操作和访问之时触发。
js
document.addEventListener('DOMContentLoaded', function() {
// 在DOM准备就绪后执行的代码
console.log('DOMContentLoaded event fired');
// 在这里可以执行需要在页面加载后立即进行的操作
});
为什么使用 DOMContentLoaded
事件?
- 提早执行 JavaScript: 使用
DOMContentLoaded
允许在页面的 DOM 结构完全构建之后立即执行 JavaScript 代码。这有助于避免在 DOM 不完全构建时尝试访问或修改 DOM 元素,从而提高代码的可靠性。 - 不等待资源加载: 相较于
load
事件,DOMContentLoaded
不需要等待页面上所有的资源(如图像、样式表)加载完成。这使得它更早触发,有助于提高页面加载性能。 - 交互性能: 在
DOMContentLoaded
时执行一些初始化脚本,可以提高页面的初始交互性能,因为用户不需要等待所有资源加载完成。
readystatechange 事件: 是在 Document
对象的 readyState
属性发生变化时触发的事件。readyState
表示文档加载的当前状态,而 readystatechange
事件则在这个状态发生变化时提供通知。
readyState
属性的可能值:
- uninitialized:对象存在并尚未初始化。
- loading:对象正在加载数据。
- loaded:对象已经加载完数据。
- interactive:对象可以交互,但尚未加载完成。
- complete:对象加载完成。
js
document.onreadystatechange = function() {
console.log('Ready state changed:', document.readyState);
if (document.readyState === 'complete') {
// 在文档完全加载后执行的代码
console.log('Document is fully loaded');
}
};
pageshow
和 pagehide
事件:是 HTML Living Standard 规范中定义的事件,用于通知开发者有关页面显示和隐藏的状态。
pageshow
事件在页面显示时触发。这可能发生在页面初次加载、前进/后退导致的页面重新显示,或者在浏览器中重新加载页面时。这个事件提供了一种方式来检测页面是否从浏览器的缓存中加载,以及是否是新加载的页面。
js
window.addEventListener('pageshow', function(event) {
console.log('Page is shown');
console.log('Persisted: ', event.persisted); // 是否从缓存加载
});
pagehide
事件在页面即将被隐藏时触发。这可能发生在页面被关闭、导航离开当前页面或刷新页面时。与 pageshow
事件一样,pagehide
事件也提供了一个属性来检测页面是否将被缓存。
js
window.addEventListener('pagehide', function(event) {
console.log('Page is about to be hidden');
console.log('Persisted: ', event.persisted); // 是否被缓存
});
hashchange
事件 :是在 URL 中的片段标识符(hash,即 URL 中的 #
及其后的部分)发生变化时触发的事件。这个事件通常与使用锚点链接或前端路由时相关。
js
window.addEventListener('hashchange', function() {
console.log('Hash changed:', window.location.hash);
});
为什么使用 hashchange
事件?
- 前端路由: 在单页面应用程序(SPA)中,常常使用锚点链接或者路由库实现前端路由。
hashchange
事件是实现这一机制的关键。 - 历史记录管理: 通过监听
hashchange
事件,你可以实现对页面历史记录的管理,包括前进和后退操作。 - 深链接: 使用片段标识符作为状态的一部分可以创建具有深链接的 Web 应用程序,使用户可以通过书签或直接输入 URL 来访问特定的应用程序状态。
内存与性能
事件委托
"过多事件处理程序"的解决方案是使用事件委托
。事件委托利用事件冒泡,可以只使用一个事件处理程序来管理一种类型的事件。
事件委托具有如下优点:
- document 对象随时可用,任何时候都可以给它添加事件处理程序(不用等待 DOMContentLoaded 或 load 事件)。这意味着只要页面渲染出可点击的元素,就可以无延迟地起作用。
- 节省花在设置页面事件处理程序上的时间。只指定一个事件处理程序既可以节省 DOM 引用,也 可以节省时间。
- 减少整个页面所需的内存,提升整体性能。
最适合使用事件委托的事件包括: click、mousedown、mouseup、keydown 和 keypress。
mouseover 和 mouseout 事件冒泡,但很难适当处理,且经常需要计算元素位置(因为 mouseout 会在光标从一个元素移动到它的一个后代节点以及移出元素之外时触发)。
删除事件处理程序
应该及时删除不用的事件处理程序。
onload 事件处理程序中做了什么,最好在 onunload 事件处理程序中恢复。
通过属性赋值添加的事件处理程序:
js
// 通过属性赋值添加事件处理程序
function myEventHandler() {
console.log('Event handled!');
}
// 添加事件处理程序
document.getElementById('myElement').onclick = myEventHandler;
// 删除事件处理程序
document.getElementById('myElement').onclick = null;
使用 addEventListener
添加的事件处理程序:
js
// 使用 addEventListener 添加事件处理程序
function myEventHandler() {
console.log('Event handled!');
}
// 添加事件处理程序
document.getElementById('myElement').addEventListener('click', myEventHandler);
// 删除事件处理程序
document.getElementById('myElement').removeEventListener('click', myEventHandler);
围绕着使用事件,需要考虑内存与性能问题。例如:
- 最好限制一个页面中事件处理程序的数量,因为它们会占用过多内存,导致页面响应缓慢;
- 利用事件冒泡,事件委托可以解决限制事件处理程序数量的问题;
- 最好在页面卸载之前删除所有事件处理程序。
模拟事件
模拟事件的方法可以通过 Event
对象和 dispatchEvent
方法来实现。
js
// 获取按钮元素
var myButton = document.getElementById('myButton');
// 创建事件对象
var clickEvent = new Event('click');
// 添加事件监听器
myButton.addEventListener('click', function(event) {
console.log('Button clicked!');
});
// 模拟触发点击事件
myButton.dispatchEvent(clickEvent);
未完待续......
参考资料
《JavaScript 高级程序设计》(第 4 版)