
2025-03-01 日报 Day112

今日的鸡汤
在心里种花,
人生才不会荒芜。
今日学习内容
1、JS 红皮书 P523-542 第十七章:事件
今日笔记
1、HTML5 事件:
- contextmenu 事件: contextmenu 事件冒泡,因此只要给 document 指定一个事件处理程序就可以处理页面上的所有同类事件。事件目标是触发操作的元素。这个事件在所有浏览器中都可以取消,在 DOM 合规的浏览器中使用 event.preventDefault()。
通常,自定义的上下文菜单都是通过 oncontextmenu 事件处理程序触发显示,并通过 onclick 事件处理程序触发隐藏的。来看下面的例子:
1 |
|
这个例子中的
元素有一个上下文菜单
- 。作为上下文菜单,
- beforeunload事件: beforeunload 事件会在 window 上触发,用意是给开发者提供阻止页面被卸载的机会。这个事件会在页面即将从浏览器中卸载时触发,如果页面需要继续使用,则可以不被卸载。这个事件不能取消,否则就意味着可以把用户永久阻拦在一个页面上。相反,这个事件会向用户显示一个确认框,其中的消息表明浏览器即将卸载页面,并请用户确认是希望关闭页面,还是继续留在页面上。
需要将 event.returnValue 设置为要在确认框中显示的字符串(对于 IE 和 Firefox 来说),并将其作为函数值返回(对于 Safari 和 Chrome 来说),如下所示:1
2
3
4
5window.addEventListener("beforeunload", (event) => {
let message = "I'm really going to miss you if you go.";
event.returnValue = message;
return message;
}); - DOMContentLoaded事件: window 的 load 事件会在页面完全加载后触发,因为要等待很多外部资源加载完成,所以会花费较长时间。而 DOMContentLoaded 事件会在 DOM 树构建完成后立即触发,而不用等待图片、JavaScript文件、CSS 文件或其他资源加载完成。相对于 load 事件,DOMContentLoaded 可以让开发者在外部资源下载的同时就能指定事件处理程序,从而让用户能够更快地与页面交互。
要处理 DOMContentLoaded 事件,需要给 document 或 window 添加事件处理程序(实际的事件目标是 document,但会冒泡到 window)。下面是一个在 document 上监听 DOMContentLoaded 事件的例子:DOMContentLoaded 事件的 event 对象中不包含任何额外信息(除了 target 等于 document)。1
2
3document.addEventListener("DOMContentLoaded", (event) => {
console.log("DOM fully loaded and parsed");
});
DOMContentLoaded 事件通常用于添加事件处理程序或执行其他 DOM操作。这个事件始终在 load事件之前触发。
对于不支持 DOMContentLoaded 事件的浏览器,可以使用超时为 0 的 setTimeout()函数,通过其回调来设置事件处理程序,比如:1
2
3setTimeout(() => {
// 在这里添加事件处理程序
}, 0); - readystatechange事件: 这个有点神秘的事件旨在提供文档或元素加载状态的信息,但行为有时候并不稳定。支持 readystatechange 事件的每个对象都有一个 readyState 属性,该属性具有一个以下列出的可能的字符串值。
uninitialized:对象存在并尚未初始化。
loading:对象正在加载数据。
loaded:对象已经加载完数据。
interactive:对象可以交互,但尚未加载完成。
complete:对象加载完成。 - pageshow与pagehide事件: Firefox 和 Opera 开发了一个名为往返缓存(bfcache,back-forward cache)的功能,此功能旨在使用浏览器“前进”和“后退”按钮时加快页面之间的切换。这个缓存不仅存储页面数据,也存储 DOM 和JavaScript 状态,实际上是把整个页面都保存在内存里。如果页面在缓存中,那么导航到这个页面时就不会触发 load 事件。通常,这不会导致什么问题,因为整个页面状态都被保存起来了。不过,Firefx决定提供一些事件,把往返缓存的行为暴露出来。
第一个事件是 pageshow,其会在页面显示时触发,无论是否来自往返缓存。在新加载的页面上,pageshow 会在 load 事件之后触发;在来自往返缓存的页面上,pageshow 会在页面状态完全恢复后触发。注意,虽然这个事件的目标是 document,但事件处理程序必须添加到 window 上。下面的例子展示了追踪这些事件的代码:除了常用的属性,pageshow 的 event 对象中还包含一个名为 persisted 的属性。这个属性是一个布尔值,如果页面存储在了往返缓存中就是 true,否则就是 false。可以像下面这样在事件处理程序中检测这个属性:1
2
3
4
5
6
7
8
9
10(function() {
let showCount = 0;
window.addEventListener("load", () => {
console.log("Load fired");
});
window.addEventListener("pageshow", () => {
showCount++;
console.log(`Show has been fired ${showCount} times.`);
});
})();与 pageshow 对应的事件是 pagehide,这个事件会在页面从浏览器中卸载后,在 unload 事件之前触发。与 pageshow 事件一样,pagehide 事件同样是在 document 上触发,但事件处理程序必须被添加到 window。event 对象中同样包含 persisted 属性,但用法稍有不同。比如,以下代码检测了event.persisted 属性:1
2
3
4
5
6
7
8
9
10
11(function() {
let showCount = 0;
window.addEventListener("load", () => {
console.log("Load fired");
});
window.addEventListener("pageshow", () => {
showCount++;
console.log(`Show has been fired ${showCount} times.`,
`Persisted? ${event.persisted}`);
});
})();1
2
3window.addEventListener("pagehide", (event) => {
console.log("Hiding. Persisted? " + event.persisted);
}); - hashchange事件: HTML5 增加了 hashchange 事件,用于在 URL 散列值(URL 最后#后面的部分)发生变化时通知开发者。这是因为开发者经常在 Ajax 应用程序中使用 URL 散列值存储状态信息或路由导航信息。
onhashchange 事件处理程序必须添加给 window,每次 URL 散列值发生变化时会调用它。event对象有两个新属性:oldURL 和 newURL。这两个属性分别保存变化前后的 URL,而且是包含散列值的完整 URL。下面的例子展示了如何获取变化前后的 URL:2、设备事件: 随着智能手机和平板计算机的出现,用户与浏览器交互的新方式应运而生。设备事件可以用于确定用户使用设备的方式。1
2
3
4
5
6
7window.addEventListener("hashchange", (event) => {
console.log(`Old URL: ${event.oldURL}, New URL: ${event.newURL}`);
});
// 如果想确定当前的散列值,最好使用 location 对象:
window.addEventListener("hashchange", (event) => {
console.log(`Current hash: ${location.hash}`);
}); - orientationchange事件: 苹果公司在移动 Safari 浏览器上创造了 orientationchange 事件,以方便开发者判断用户的设备是处于垂直模式还是水平模式。移动 Safari 在 window 上暴露了 window.orientation 属性,它有以下 3 种值之一:0 表示垂直模式,90 表示左转水平模式(主屏幕键在右侧),–90 表示右转水平模式(主屏幕键在左)。所有 iOS 设备都支持 orientationchange 事件和 window.orientation 属性。
- deviceorientation事件: 如果可以获取设备的加速计信息,而且数据发生了变化,这个事件就会在 window 上触发。要注意的是,deviceorientation 事件只反映设备在空间中的朝向,而不涉及移动相关的信息。设备本身处于 3D 空间即拥有 x 轴、y 轴和 z 轴的坐标系中。如果把设备静止放在水平的表面上,那么三轴的值均为 0,其中,x 轴方向为从设备左侧到右侧,y 轴方向为从设备底部到上部,z 轴方向为从设备背面到正面。
1
2
3
4
5window.addEventListener("deviceorientation", (event) => {
let output = document.getElementById("output");
output.innerHTML =
`Alpha=${event.alpha}, Beta=${event.beta}, Gamma=${event.gamma}<br>`;
}); - devicemotion事件: DeviceOrientationEvent 规范也定义了 devicemotion 事件。这个事件用于提示设备实际上在移动,而不仅仅是改变了朝向。例如,devicemotion 事件可以用来确定设备正在掉落或者正拿在一个行走的人手里。
当 devicemotion 事件触发时,event 对象中包含如下额外的属性。
acceleration:对象,包含 x、y 和 z 属性,反映不考虑重力情况下各个维度的加速信息。
accelerationIncludingGravity:对象,包含 x、y 和 z 属性,反映各个维度的加速信息,包含 z 轴自然重力加速度。
interval:毫秒,距离下次触发 devicemotion 事件的时间。此值在事件之间应为常量。
rotationRate:对象,包含 alpha、beta 和 gamma 属性,表示设备朝向。3、触摸及手势事件: 因为 iOS 设备没有鼠标和键盘,所以常规的鼠标和键盘事件不足以创建具有完整交互能力的网页。同时,WebKit 也为 Android 定制了很多专有事件,成为了事实标准,并被纳入 W3C 的 Touch Events 规范。本节介绍的事件只适用于触屏设备。1
2
3
4
5window.addEventListener("devicemotion", (event) => {
let output = document.getElementById("output");
output.innerHTML =
`Acceleration: X=${event.acceleration.x}, Y=${event.acceleration.y}, Z=${event.acceleration.z}<br>`;
}); - 触摸事件:
touchstart:手指放到屏幕上时触发(即使有一个手指已经放在了屏幕上)。
touchmove:手指在屏幕上滑动时连续触发。在这个事件中调用 preventDefault()可以阻止滚动
touchend:手指从屏幕上移开时触发。
touchcancel:系统停止跟踪触摸时触发。文档中并未明确什么情况下停止跟踪。 - 手势事件:
手势事件会在两个手指触碰屏幕且相对距离或旋转角度变化时触发。手势事件有以下 3 种。
gesturestart:一个手指已经放在屏幕上,再把另一个手指放到屏幕上时触发。
gesturechange:任何一个手指在屏幕上的位置发生变化时触发。
gestureend:其中一个手指离开屏幕时触发。
4、内存与性能: 因为事件处理程序在现代 Web 应用中可以实现交互,所以很多开发者会错误地在页面中大量使用它们。在 JavaScript 中,页面中事件处理程序的数量与页面整体性能直接相关。原因有很多。首先,每个函数都是对象,都占用内存空间,对象越多,性能越差。其次,为指定事件处理程序所需访问 DOM 的次数会先期造成整个页面交互的延迟。只要在使用事件处理程序时多注意一些方法,就可以改善页面性能。 - 事件委托: “过多事件处理程序”的解决方案是使用事件委托。事件委托利用事件冒泡,可以只使用一个事件处理程序来管理一种类型的事件。例如,click 事件冒泡到 document。这意味着可以为整个页面指定一个 onclick 事件处理程序,而不用为每个可点击元素分别指定事件处理程序。比如有以下 HTML:这里的 HTML 包含 3 个列表项,在被点击时应该执行某个操作。对此,通常的做法是像这样指定 3个事件处理程序:
1
2
3
4
5<ul id="myLinks">
<li id="goSomewhere">Go somewhere</li>
<li id="doSomething">Do something</li>
<li id="sayHi">Say hi</li>
</ul>如果对页面中所有需要使用 onclick 事件处理程序的元素都如法炮制,结果就会出现大片雷同的只为指定事件处理程序的代码。使用事件委托,只要给所有元素共同的祖先节点添加一个事件处理程序,就可以解决问题。比如:1
2
3
4
5
6
7
8
9
10
11
12let item1 = document.getElementById("goSomewhere");
let item2 = document.getElementById("doSomething");
let item3 = document.getElementById("sayHi");
item1.addEventListener("click", (event) => {
location.href = "http:// www.wrox.com";
});
item2.addEventListener("click", (event) => {
document.title = "I changed the document's title";
});
item3.addEventListener("click", (event) => {
console.log("hi");
});只要可行,就应该考虑只给 document 添加一个事件处理程序,通过它处理页面中所有某种类型的事件。相对于之前的技术,事件委托具有如下优点。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15let list = document.getElementById("myLinks");
list.addEventListener("click", (event) => {
let target = event.target;
switch(target.id) {
case "doSomething":
document.title = "I changed the document's title";
break;
case "goSomewhere":
location.href = "http:// www.wrox.com";
break;
case "sayHi":
console.log("hi");
break;
}
});
document 对象随时可用,任何时候都可以给它添加事件处理程序(不用等待 DOMContentLoaded或 load 事件)。这意味着只要页面渲染出可点击的元素,就可以无延迟地起作用。
节省花在设置页面事件处理程序上的时间。只指定一个事件处理程序既可以节省 DOM 引用,也可以节省时间。
减少整个页面所需的内存,提升整体性能。 - 删除事件处理程序: 把事件处理程序指定给元素后,在浏览器代码和负责页面交互的 JavaScript 代码之间就建立了联系。这种联系建立得越多,页面性能就越差。除了通过事件委托来限制这种连接之外,还应该及时删除不用的事件处理程序。很多 Web 应用性能不佳都是由于无用的事件处理程序长驻内存导致的。
- 元素初始时是隐藏的。以下是实现上下文菜单功能的 JavaScript 代码:
1 | window.addEventListener("load", (event) => { |
这里在
元素上指定了一个 oncontextmenu 事件处理程序。这个事件处理程序首先取消默认行,确保不会显示浏览器默认的上下文菜单。接着基于 event 对象的 clientX 和 clientY 属性把
- 元素放到适当位置。最后一步通过将 visibility 属性设置为”visible”让自定义上下文菜单显示出来。另外,又给 document 添加了一个 onclick 事件处理程序,以便在单击事件发生时隐藏上下文菜单(系统上下文菜单就是这样隐藏的)。
此页目录
2025-03-01 日报 Day112