目录
前言
一、事件流
1、事件冒泡
2、事件捕获
3、DOM 事件流
二、事件处理程序
1、HTML 事件处理程序(了解)
2、DOM0 事件处理程序(了解)
3、DOM2 事件处理程序
4、IE 事件处理程序
5、跨浏览器的事件处理程序
三、事件对象
1、DOM 中的事件对象
2、IE 中的事件对象
3、跨浏览器的事件对象
四、事件类型
1、UI 事件
(1)、load 事件
(2)、unload 事件
(3)、resize 事件
(4)、scroll 事件
2、焦点、鼠标与滚轮事件
(1)、焦点事件
(2)、鼠标事件
(3)、滚轮事件
3、键盘与文本事件
(1)、键码
(2)、字符编码
(3)、DOM3 级键盘事件的变化
(4)、textInput 事件
4、复合事件
5、变动事件
(1)、删除节点
(2)、插入节点
6、HTML5 事件
(2)、beforeunload 事件
(3)、DOMContentLoaded 事件
(4)、readystatechange 事件
(5)、pageshow 和 pagehide 事件
(6)、hashchange 事件
7、设备事件
(1)、orientationchange 事件
(2)、deviceorientationevent 事件
(3)、devicemotion 事件
8、触摸与手势事件
(1)、兼容 DOM 的触摸事件
(2)、跟踪触摸的事件
(3)、手势事件
五、内存和性能
1、事件委托
2、移除事件处理程序
六、事件循环(Event Loop)
1、进程与线程
2、宏队列与微队列
(1)、宏队列
(2)、微队列
3、JavaScript 执行代码的具体顺序
4、Event Loop 实例
七、事件模拟
1、DOM 中的事件模拟 和 IE 中的事件模拟事件
本文会用到 EventUtil 事件处理程序,详情请戳:https://blog.csdn.net/mChales_Liu/article/details/106541683
本文包含 EventUtil 跨浏览器事件处理程序的部分完善过程。
JavaScript 与 HTML 之间交互是通过事件实现的。事件就是用户或浏览器自身执行的某种动作,是文档或浏览器窗口中发生的一些特定的交互瞬间。
我们可以使用侦听器或处理程序来预定事件,以便事件发生时执行相应的代码。
当触发某个事件时,事件从子元素到父元素或者从父元素到子元素的过程叫做事件流。
IE 和 Netscape 开发团队分别提出了完全相反的事件流:IE 的事件流是冒泡流;Netscape 的事件流是捕获流。
DOM 2 级规定的事件流,包括三个阶段:
IE 事件流叫做事件冒泡:事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。
IE8 及其更早版本只支持冒泡流。
例如:
Document
如果你单击了页面中的
事件冒泡的演示:
/* style 中 */
/* html 中 */
/* JavaScript 中 */
我只点击紫红色的 son 元素一下,事件就开始冒泡了,请看效果:
阻止事件冒泡
son.onclick = function(e){
e = e || window.event;
// 阻止事件冒泡(兼容 IE 8 及其以下)
e.stopPropagation ? e.stopPropagation() : e.cancelBubble = true;
alert("son 被触发");
}
Netscape 的事件流叫做事件捕获:越不太具体的节点越先接收事件,越具体的节点越后接收事件。事件补货的用意在于在事件到达预定目标之前捕获它。
例如:
Document
如果你单击了页面中的
事件捕获的演示:
/* style 中 */
.parent{
width: 400px;
height: 400px;
background: #008000;
margin-left:400px;
}
.son{
display: block;
width: 180px;
height: 180px;
background: #f0f;
margin: 5px;
}
/* html 中 */
/* JavaScript 中 */
上述代码中, addEventListener() 方法的第三个参数决定了事件的方式——捕获或者冒泡,true为捕获,false为冒泡,默认为false。后面会细讲 addEventListener() 方法的。
我只点击紫红色的 son 元素一下,事件就开始捕获了,请看效果:
阻止事件捕获
window.addEventListener("click", function(e){
e = e || window.event;
e.stopPropagation();// 阻止事件捕获
alert("window 被触发");
}, true);
由上述代码可知:stopPropagation() 方法可以阻止事件的进一步传播(包括 冒泡 和 捕获)。
DOM2 事件流包括三个阶段:事件捕获阶段、处于目标阶段 和 事件冒泡阶段。
DOM2 明确规定:事件捕获仅仅为截获事件提供机会,不会接收到事件目标。多数支持 DOM 事件流的浏览器都实现了一种特定的行为,但仍有一些浏览器依然支持“事件捕获和事件冒泡都可以接收到事件。”
IE8 及其更早版本不支持 DOM 事件流。
例如:
Document
如果你单击了页面中的
响应某个事件的函数叫做事件处理程序(或事件侦听器)。
一个元素支持的每种事件,都可以用一个与相应事件处理程序同名的 HTML 特性来制定。这个特性的值应该是能够执行的 JavaScript 代码。比如:
上述代码,通过指定 onClick 特性,将要执行的具体动作作为它的值来定义。当然,也可以选择调用在页面其他地方自定义的脚本,比如:
HTML 事件处理程序存在三大问题:
DOM0 事件处理程序就是讲一个函数 赋值 给一个事件处理程序属性。
优点是:简单、能够跨浏览器执行。
缺点是:同一个事件处理程序添加多次,后添加的会覆盖之前的。
// html 中
// DOM0 事件处理程序
var myDiv = document.getElementById("myDiv");
// 添加 DOM0 事件处理程序
myDiv.onmouseenter = function(){
console.log("Hello world"); // "Hello world"
};
myDiv.onmouseout = function(){
console.log("Bye-bye");
}
myDiv.onmouseout = function(){
console.log("Get out"); // "Get out"
}
// 删除 DOM0 事件处理程序
myDiv.onmouseenter = null;
myDiv.onmouseout = null;
DOM2 定义了两个操作事件处理程序的方法:
比如:
// html 中
// JavaScript 中
var btn = document.getElementById("btn");
btn.addEventListener("click", function(){
alert("hello world"); // hello world
});
btn.removeEventListener("click", function(){
alert("hello world");
});
// 删除失败,click 事件依然有效。改成下面这样写就能删除 click 事件了:
var handler = function(){
alert("hello world");
};
btn.addEventListener("click", handler);
btn.removeEventListener("click", handler);
IE 实现了与 DOM 类似的两个方法:
想要跨浏览器实现事件处理程序,目前有两种途径:
要保证事件处理程序能在大多数浏览器下一致的运行,只需要关注冒泡阶段!!!
下面列举一个跨浏览器实现事件处理程序的案例:
// 封装一个跨浏览器的事件处理程序对象
var EventUtil = {
addHandler: function(element, type, handler){
if(element.addEventListener){
element.addEventListener(type, handler, false);
} else if (element.attachEvent){
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
},
removeHandler: function(element, type, handler){
if(element.removeEventListener){
element.removeEventListener(type, handler, false);
} else if (element.detachEvent){
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
}
}
// 使用这个对象添加、删除跨浏览器事件处理程序
var handler = function(){
alert(hello world);
};
EventUtil.addHandler(btn, "clcik", handler);
EventUtil.removeHandler(btn, "clcik", handler);
不过,上述实现方案仍然存在以下问题:
触发某个事件时生成的对象叫做事件对象。事件对象包含着所有与事件相关的信息。
DOM 中的事件对象是 event 对象。
在 DOM0 中 event 对象是 window 对象的一个属性,在 DOM2 中 event 对象就是 event 对象。
event 对象有以下属性:
属性 | 类型 | 读/写 | 描述 |
---|---|---|---|
detail | Integer | 只读 | 与事件相关的细节信息。 |
target | Element | 只读 | 事件的目标。 |
currentTarget | Element | 只读 | 其事件处理程序当前正在处理事件的那个元素。 |
type | String | 只读 | 被触发的事件类型。 |
cancelable | Boolean | 只读 | 表明是否可以取消事件的默认行为。 |
defaultPrevented | Boolean | 只读 | 为 true 表示已经调用了 preventDefault() 方法。(DOM3 新增) |
eventPhase | Integer | 只读 | 调用事件处理程序的阶段:1 表示捕获阶段;2 表示目标阶段;3 表示冒泡阶段。 |
bubbles | Boolean | 只读 | 表明事件是否冒泡。 |
view | AbstractView | 只读 | 与时间关联的抽象视图。等同于发生事件的 window 对象。 |
trusted | Boolean | 只读 | 为 true 表示事件是浏览器生成的;为 false 表示事件是由开发人员通过 JavaScript 创建的。(DOM3 新增) |
event 对象有以下方法:
IE 的 event 对象有以下属性:
IE 浏览器中,根据添加事件处理程序的不同方式,访问 event 对象的方式分为三种:
// DOM0
var btn = document.getElementById("myBtn");
btn.onclick = function(){
var event = window.event;
alert(event); // "click"
};
// attachEvent 方法
var btn = document.getElementById("myBtn");
var handle = function(event){
alert(event); // "click"
}
btn.addachEvent("onclick", handle);
// HTML
// "click"
// EventUtil.js 封装
var EventUtil = {
// 添加事件处理程序
addHandler: function(element, type, handler){
if(element.addEventListener){
element.addEventListener(type, handler, false);
} else if (element.attachEvent){
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
},
// 删除事件处理程序
removeHandler: function(element, type, handler){
if(element.removeEventListener){
element.removeEventListener(type, handler, false);
} else if (element.detachEvent){
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
},
// 获取 event 对象
getEvent: function(){
return event ? event : window.event;
},
// 获取事件目标
getTarget: function(){
return event.target || event.srcElement;
},
// 取消默认事件行为
preventDefault: function(){
if(event.preventDefault){
event.preventDefault();
} else {
event.returnValue = false;
}
},
// 阻止事件冒泡
stopPropagation: function(){
if(event.stopPropagation){
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
}
事件类型大致可分为:
一般,在 window 上触发的任何事件都可以在 元素中通过相应的特性来指定。
DOM3 级事件是在 DOM0 级和 DOM2 级事件的基础上重新定义了DOM 的事件。包括 IE 9 在内的所有主流浏览器都支持 DOM2 级和 DOM3 级的事件。
如何检测浏览器是否支持 DOM2 级或 DOM3 级规定的 HTML 事件呢?
var isSupported = document.implementation.hasFeature("HTMLEvents", "2.0");
// 或
var isSupported = document.implementation.hasFeature("HTMLEvents", "3.0");
只有根据 DOM2 级或 DOM3 级事件实现这些事件的浏览器才会返回 true,而以非标准方式支持这些事件的浏览器则会返回 false。
当页面完全加载完毕后(所有图像、js文件、css文件等),就会触发window上面的load事件。
这个事件在 window 上面触发,因此,可以通过 JavaScript 或者
元素中的 onresize 特性来指定事件处理程序:--> 第一种方式是使用 JavaScript 的跨浏览器事件处理程序:
EventUtil.addHandler(window, "load", function(event){
alert("Loaded");
});
--> 第二种方式是为
元素添加一个 onload 特性:
load 事件案例
其实,在 DOM2 级事件规范中,应该在 document 而非 window 对象上触发 load 事件,但是,所有的浏览器都在 window 上实现了该事件,以确保向后兼容。另外,还有一些非标准的方式支持 load 事件,比如:在 IE9+、Firefox、Opera、Chrome 和 Safari3+ 中,
效果:
⑤、探索鼠标点击 button 按钮时对 mousedown 和 mouseup 事件的影响
由于只要在同一个元素上相继触发 mousedown 和 mouseup 事件就会触发 click 事件,所以,当我们单击时,会先触发 mousedown 和 mouseup 事件。 对于 mousedown 和 mouseup 事件,DOM 给 event 对象提供了一个 button 属性(注意不是
DOM 中 event.button 属性有三个值:
IE8 及之前的版本中,event.button 属性的值与 DOM 中的差异很大:
为了兼容 IE 必须另辟蹊径。我们可以用 hasFuture() 方法来检测,可以把下面代码添加到 EventUtil 对象中:
EventUtil = {
// 省略了其它代码
// 鼠标按钮事件
getButton: function(event){
if(document.implementation.hasFeature("MouseEvents", "2.0")){
return event.button;
}else{// 兼容 IE
switch (event.button) {
case 0:
case 1:
case 3:
case 5:
case 7:
return 0;
case 2:
case 6:
return 2;
case 4:
return 1;
}
}
}
// 省略了其它代码
}
可以像下面这样使用 EventUtil.getButton() 方法:
/* css 中 */
/* html 中 */
/* JavaScript 中 */
效果:
点击主鼠标按钮:00
点击滚轮按钮:11
点击次鼠标按钮:22
⑥、键盘上的修改键协同鼠标触发 鼠标事件
鼠标事件主要是鼠标来触发的,其实一些键盘上的修改键也可以影响到鼠标事件的行为,这些修改键包括 Shift、Ctrl、Alt、Meta(window 上的 Windows 键 / 苹果机上的 Cmd 键)。
修改键的状态分别是 shiftKey、ctrlKey、altKey、metaKey(IE8 及其以前的版本不支持),这些属性包含的都是布尔值,如果相应的键被按下了,则为 true,否则为 false。
当某个鼠标事件发生时,通过检测这几个属性就可以确定用户是否同时按下了其中的键。
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function(event){
event = EventUtil.getEvent(event);
var keys = [];
if(event.shiftKey){
keys.push("shift");
}
if(event.ctrlKey){
keys.push("ctrl");
}
if(event.altKey){
keys.push("alt");
}
if(event.metaKey){
keys.push("meta");
}
alert(`点击的同时按下的键:${keys.join(",")}`);
});
⑦、event.detail 属性为鼠标单击事件计数
DOM2 级事件提供了 detail 属性,用于给出有关事件的更多信息。对于鼠标事件来说,detail 中包含了一个数值,表示在给定位置上发生了多少次单击。detail 属性从 1 开始计数,每次单击发生后都会递增。如果鼠标在 mousedown 和 mouseup 之间移动了位置,则 detail 会被重置为 0。
当用户通过鼠标滚轮与页面交互时就会触发 mousewheel 事件,这个事件可以在页面内任何元素上触发,最终会冒泡到 document(IE8)或 window(IE9、Opera、Chrom 及 Safari)对象。与 mousewheel 事件对应的 event 对象有一个 wheelDelta 属性。
wheelDelta 属性:当用户向前(向上)滚动滚轮时,wheelDelta 是 120 的倍数;当向后(向下)滚动滚轮时,wheelDelta 是 -120 的倍数。通过监听 mousewheel 事件的 wheelDelta 属性判断鼠标向那个方向滚动了:
EventUtil.addHandler(document, "mousewheel", function (event) {
e = EventUtil.getEvent(event);
if (e.wheelDelta) { //判断浏览器IE,谷歌滑轮事件
if (e.wheelDelta > 0) { //当滑轮向上滚动时
alert('上滚')
}
if (e.wheelDelta < 0) { //当滑轮向下滚动时
alert('下滚')
}
} else if (e.detail) { //Firefox滑轮事件
if (e.detail> 0) { //当滑轮向下滚动时
alert('下滚')
}
if (e.detail< 0) { //当滑轮向上滚动时
alert('上滚')
}
}
});
另外,在 Opera 9.5 之前的版本中,wheelDelta 值的正负号是颠倒的。在 Firefox 中,当鼠标滚动滚轮时,支会触发一个名为 DOMMouseScroll 的滚轮事件,而有关鼠标的滚轮的信息则保存在 detail 属性中,当鼠标向前滚动时,这个属性的值是 -3 的倍数,当鼠标向后滚动时,这个属性的值是 3 的倍数。
综上,鼠标滚轮事件的跨浏览器版代码也可以添加进 EventUtil 对象里面了:
可以像下面这样使用 EventUtil.getWheelDelta() 方法:
(function(){
function handleMouseWheel(event){
event = EventUtil.getEvent(event);
var delta = EventUtil.getWheelDelta(event);
alert(delta);
}
EventUtil.addHandler(document, "mousewheel", handleMouseWheel);
EventUtil.addHandler(document, "DOMMouseScroll", handleMouseWheel);
})();
上述代码,将相关代码放在了私有作用域中,从而不会影响全局作用域。
“DOM3级事件”为键盘事件制订了规范:
键码表请戳这里:https://blog.csdn.net/mChales_Liu/article/details/106751768
IE9、Firefox、Chrome 和 Safari 浏览器的 event 对象都支持一个 charCode 属性,IE8 及之前的版本和 Opera 浏览器则是在 keyCode 中保存字符的 ASCII 编码。为了实现跨浏览器获取字符编码,我们可以将下面代码添加到 EventUtil 对象中:
var EventUtil = {
// 省略的代码
getCharCode: function(){
if(typeof event.charCode == "number"){
return event.charCode;
}else{
return event.keyCode;
}
}
// 省略的代码
};
可以像下面这样使用 EventUtil.getCharCode() 方法:
var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "keypress", function(event){
event = EventUtil.getEvent(event);
alert(EventUtil.getCharCode(event));
});
上述代码中,在取得字符编码后,可以用 String.fromCharCode() 方法将其转换成实际的字符。
①、key 和 char 属性(兼容 keyIdentifier 属性)
DOM3级事件中的键盘事件,不再包含charCode属性,而是包含两个新属性:key 和 char。
兼容性:
这样解决这个兼容问题:
var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "keypress", function(event){
event = EventUtil.getEvent(event);
var identifier = event.key || event.keyIdentifier;// 兼容
if(identifier){
alert(identifier);
}
});
②、location 属性(兼容 keyLocation 属性)
location 属性表示按下了什么位置上的键:
兼容性:
这样解决这个兼容问题:
var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "keypress", function(event){
event = EventUtil.getEvent(event);
var location = event.location || event.keyLocation;// 兼容
if(location){
alert(location);
}
});
③、getModifierState() 方法
getModifierState() 方法接收一个参数——修改键(“Shift”、“AltGraph”、“Control”或“Meta”)。当指定的修改键是活动的(也就是处于被按下状态),这个方法返回 true,否则返回 false。
实际上,通过 event 对象的 shiftKey、altKey、ctrlKey 和 metaKey 属性已经可以取得类似的属性了。
IE9 是唯一支持 getModifierState() 方法的浏览器。
当用户在可编辑区域中输入字符时,就会触发 textInput 事件。
textInput 事件与 keypress 事件的区别:
①、event.data 属性
textInput 事件主要考虑的是字符,在 event 对象中包含一个 data 属性,它返回用户输入的字符(而非字符编码)。
这样使用 textInput 事件里的 event.data 属性:
var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "textInput", function(event){
event = EventUtil.getEvent(event);
alert(event.data);
});
注意:只能在 DOM2 事件处理程序——addEventListener() 方法中获取 event.data 属性的值。
②、event.inputMethod 属性
dvent对象上的属性,表示把文本输入到文本框中的方式:
复合事件是 DOM3 级事件中新添加的一类事件,用于处理 IME 的输入序列。
复合事件有以下三中:
复合事件的 data 属性:
DOM2级的变动事件,能在DOM中的一部分发生变化时给出提示。
变动事件是为 XML 或 HTML DOM 设计的,并不特定于某种语言。
DOM2级定义了如下变动事件:
可以使用以下代码判断浏览器对变动事件的支持性:
var isSupported = document.implementation.hasFeature("MutationEvents","2.0");
DOMNodeRemoved 和 DOMNodeRemovedFromDocument 事件。
当使用 removeChild() 或 replace Child() 从 DOM 中删除节点时:
DOMNodeInserted 和 DOMNodeInsertedIntoDocument 事件。
在使用 appendChild()、replaceChild() 或 insertBefore() 方法向 DOM 中插入节点时:
DOM 规范没有涵盖所有浏览器支持的所有事件,HTML5 详尽列出了浏览器应该支持的所有事件。
一般情况下,在 Windows 中右击鼠标显示上下文菜单,在 Mac 中 Ctrl+单击显示上下文菜单。何时应该显示上下文菜单,以便开发者取消默认的上下文菜单,转而提供自定义的菜单呢?请看 contextmenu 事件:
可以像下面这样使用 contextmenu 事件:
web 自定义上下文菜单基本结构(★★★★★)
/* html 中 */
- One
- Two
- Three
/* JavaScript 中 */
beforeunload 事件是为了满足“在页面卸载之前阻止这一操作”:
可以像下面这样使用 beforeunload 事件:
在页面关闭之前询问用户是否真的要关闭页面(★★★★★)
// 当离开时会显示信息和两个按钮,当刷新时也会提示(是否确定离开此页面)
EventUtil.addHandler(window, "beforeunload", function(event){
event = EventUtil.getEvent(event);
var message = "do you realy want to leave?";
event.returnValue = message;
return message;
});
window 对象的 load 事件会在页面中所有资源都加载完毕时触发,但这个过程可能会因为要加载的外部资源过多而颇费周折。如何不必等待所有资源都加载完毕就能为页面添加事件处理程序呢?请看 DOMContentLoaded 事件:
可以像下面这样使用 beforeunload 事件:
不必等待引入的资源都加载完毕就能与页面交互(★★★★★)
EventUtil.addHandler(document, "DOMContentLoaded", function(event){
alert("内容加载完毕。");
// 与页面交互吧
});
对于不支持该事件的浏览器版本,理想情况下,可以设置一个时间为 0 毫秒的延时器,强制 load 事件允许在并未加载完所有资源的情况下与页面交互:
setTimeout(function(){
alert("内容加载完毕。");
// 在此与页面交互
}, 0);
注意:这里不保证在所有的环境下该超时调用一定会早于 load 事件被触发。
IE 提供的事件,支持该事件的每个对象都有一个 readyState 属性,可能包含下列 5 个值中的一个:
并非每个对象都会经历 readyState 属性的这些阶段。
对于 document 对象,值为 interactive 的 readyState 会在与 DOMContentLoaded 大致相同的时刻触发 readystatechange 事件。
readystatechange 事件没有目标(event.target),它的 event 对象不包含其它信息。
支持 readystatechange 事件的浏览器有:IE、Firefox4+ 和 Opera。
readystatechange 事件在与 load 事件一起使用时,无法预测两个事件触发的先后顺序。交互阶段可能早于或晚于完成阶段出现,无法确保顺序。在包含较多外部资源的页面中,交互阶段更有可能出现的早;在包含较少外部资源的页面中,完成阶段更有可能出现的早。因此,为了尽可能抢到先机,有必要同时检测交互和完成阶段,如:
EventUtil.addHandler(document, "readystatechange", function(event){
if (document.readyState == "interactive" || document.readyState == "complete") {
// 防止多次触发 readystatechange 事件
EventUtil.removeHandler(document, "readystatechange", arguments.callee);
alert("文档加载完毕");
}
});
这样使用 readystatechange 事件与使用 DOMContentLoaded 事件的效果十分相近了。
另外,可以用 readystatechange 事件来监听使用 JavaScript 动态加载的 JavaScript(IE 和 Opera 中) 和 CSS(IE 中) 文件是否已经加载完成(浏览器都有一个特点:只有把动态创建的元素添加到页面中,浏览器才会开始下载该外部资源)。readyState 属性无论等于 loaded 或 complete 都可以表示资源已经加载完成,有时候,readyState 属性会停在 loaded 阶段而永远不会“完成”;有时候,又会跳过 loaded 阶段直接“完成”。于是需要这样监听一下:
监听动态加载 JavaScript 是否完成:
EventUtil.addHandler(window, "load", function(){
var script = document.createElement("script");
EventUtil.addHandler(script, "readystatechange", function(event){
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
if (document.readyState == "loaded" || document.readyState == "complete") {
EventUtil.removeHandler(target, "readystatechange", arguments.callee);
alert("script 文件加载完毕");
}
});
script.src = "client.js";
document.body.appendChild(script);
});
监听动态加载 CSS 是否完成:
EventUtil.addHandler(window, "load", function(){
var link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
EventUtil.addHandler(link, "readystatechange", function(event){
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
if (document.readyState == "loaded" || document.readyState == "complete") {
EventUtil.removeHandler(target, "readystatechange", arguments.callee);
alert("CSS 文件加载完毕");
}
});
link.href = "public.css";
document.getElementsByTagName("head")[0].appendChild(link);
});
Firefox 和 Opera 中有一个特性名叫“往返缓存”(back-forward cache 或 bfcache)。
bfcache 不仅保存着页面的数据,还保存着 DOM 和 JavaScript 的状态;实际上是将整个页面都保存在了内存里。
bfcache 可以在用户使用浏览器的“前进”和“后退”按钮时加快页面的转换速度——如果页面位于 bfcache 中,那么再次打开该页面时就不会触发load事件。
Firefox 为 bfcache 提供了两个新事件:pageshow 和 pagehide 事件。
使用案例:
(function(){
var showCount = 0;
EventUtil.addHandler(window, "load", function(){
alert("页面加载完成");
});
EventUtil.addHandler(window, "pageshow", function(event){
showCount++;
console.log(`页面从后台第${showCount}次转为前台,persisted 的值是${event.persisted}`);
});
})();
EventUtil.addHandler(window, "pagehide", function(event){
alert(`页面是否已转为后台运行${event.persisted}`);
});
上述代码使用了私有作用域,避免了污染全局作用域。
注意:指定了 unload 事件处理程序的页面会被自动排除在 bfcache 之外,即使事件处理程序是空的,原因在于,unload 最常用于撤销在 load 中执行的操作,而跳过 load 后再次显示页面很可能会导致页面不正常。
hashchange 事件的作用:当 URL 的参数列表(及 URL 中“#”后面的所有字符串)发生变化时,通知开发人员。
必须要把 hashchange 事件添加给 window 对象。
hashchange 事件的 event 对象新增了两个属性:oldURL 和 newURL。这两属性分别保存着参数列表变化前后的完整 URL。
支持 hashchange 事件的浏览器有 IE8+、Firefox3.6+、Safari5+、Chrome 和 Opera10.6+。
支持 oldURL 和 newURL 属性的只有 Firefox6+、Chrome 和 Opera10.6+。因此,最好使用 location 对象来确定当前参数列表。
EventUtil.addHandler(window, "hashchange", function(event){
alert(`当前 hash 值${location.hash}`);
});
使用下面的方法可以跨浏览器检测浏览器是否支持 hashchange 事件:
var isSupported = ("onhashchange" in window) && (document.documentMode === undefined || document.documentMode > 7);
该事件用来确定用户何时将设备从横向查看模式切换到纵向查看模式。
该事件的 event 对象不包含任何信息。
该事件的 window.orientation 属性中包含 3 个值:0 表示肖像模式、90 表示左旋转的横向模式、-90 表示右旋转的横向模式。
该事件的使用典例:
EventUtil.addHandler(window, "load", function(event){
var div = document.getElementById("myDiv");
div.innerHTML = `1-当前 orientation 是:${window.orientation}`;
EventUtil.addHandler(window, "orientationchange", function(event){
div.innerHTML = `2-当前 orientation 是:${window.orientation}`;
});
});
这个例子中,当触发 load 事件时会显示最初的方向信息。添加了 orientationchange 事件处理程序后,只要发生这个事件,就会有表示新方向的信息更新在 div 里的值。
该事件是一个实验中的功能,支持该事件的浏览器有 Chrome、Firefox6+、Opera4.2+ 和 Safari;该事件在 IE 和 Opera 浏览器中没有实现。
该事件在 window 对象上触发。
该事件用来告知开发人员设备在空间中朝向哪里。设备在三维空间中是靠x、y、z轴来定位的:
事件对象包含 5 个属性:
该事件的使用典例:
EventUtil.addHandler(window, "deviceorientation", function(event){
var output = document.getElementById("output");
output.innerHTML = `alpha:${event.alpha}, beta:${event.beta}, gamma:${event.gamma}`;
});
EventUtil.addHandler(window, "deviceorientation", function(event){
var arrow = document.getElementById("arrow");
arrow.style.webkitTransform = `rotate(${Math.round(event.alpha)}deg)`;
});
该事件是要告诉开发者设备什么时候移动,而不仅仅是设备方向如何改变。如通过该事件检测设备是否在往下掉,是否被走着的人拿在手里等等。
该事件的 event 对象包含以下属性:
以上属性,如果读取不到值,就会返回 null,所以应该先检测它们的值是否为 null。
该事件的使用典例:
EventUtil.addHandler(window, "devicemotion", function(event){
var output = document.getElementById("output");
if(event.rotationRate !== null){
output.innerHTML += `alpha:${event.rotationRate.alpha}, beta:${event.rotationRate.beta}, gamma:${event.rotationRate.gamma}`;
}
});
上面的事件都会冒泡,都可以取消。每个触摸事件的 event 对象都提供了在鼠标事件中常见的属性。
每个 Touch 对象包含的属性:
function handleTouchEvent () {
if (event.touches.length == 1) {
var output = document.getElementById("output");
switch (event.type) {
case "touchstart":
output.innerHTML = "Touch started (" + event.touches[0].clientX + "," + event.touches[0].clientY + ")";
break;
case "touchmove":
event.preventDefault();
output.innerHTML = "Touch moved (" + event.changedTouches[0].clientX + "," + event.changedTouches[0].clientY + ")";
break;
case "touchend":
output.innerHTML = "Touch ended (" + event.changedTouches[0].clientX + "," + event.changedTouches[0].clientY + ")";
break;
}
}
}
window.addEventListener("touchstart", handleTouchEvent);
window.addEventListener("touchmove", handleTouchEvent);
window.addEventListener("touchend", handleTouchEvent);
其中event还包括除鼠标事件的属性之外另外两个属性:rotation和scale:其中rotation属性表示旋转的角度,从0开始,负值表示逆时针旋转,正值表示顺时针旋转;scale属性从1开始,随着距离拉大而增加,随着距离缩小而减小。
function handleGestureEvent() {
var output = document.getElementById("output");
switch (event.type) {
case "gesturestart":
output.innerHTML = "rotation: " + event.rotation + ";" + "scale: " + event.scale;
break;
case "gestureend":
output.innerHTML = "rotation: " + event.rotation + ";" + "scale: " + event.scale;
break;
case "gesturechange":
event.preventDefault();
output.innerHTML = "rotation: " + event.rotation + ";" + "scale: " + event.scale;
break;
}
}
document.addEventListener("gesturestart", handleGestureEvent);
document.addEventListener("gestureend", handleGestureEvent);
document.addEventListener("gesturechange", handleGestureEvent);
每当将事件处理程序指定给元素时,运行中的浏览器代码与支持页面交互的 JavaScript 代码之间就会建立一个链接。这种连接越多,页面执行的就越慢。我们可以采用:事件委托 或者 手动移除事件处理程序 来优化内存和性能。
对“事件处理程序过多”问题的解决方案——事件委托——只制定一个事件处理程序,来管理某一类型的所有事件。
事件委托利用了事件冒泡。
例如:
事件委托
- go Somewhere
- do Something
- say Hi
vue 中的事件委托:https://blog.csdn.net/mChales_Liu/article/details/108849274
在开发过程中或许会遇到这样的需求:有一个按钮包含在
如果只是修改按钮父组件的 innerHTML 属性值,按钮是被移除了,但是这个按钮还带着一个事件处理程序呢。这时,不但 IE 浏览器可能会抛出错误,而且还会浪费内存,降低性能。怎么办呢?如果你知道某个元素即将被移除,那么最好手工移除其事件处理程序。
Event Loop:是一种 JavaScript 提供的机制,用来解决单线程运行带来的一些问题。
进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程——资源分配的最小单位。
线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流。线程——程序执行的最小单位。
一些异步任务的回调会依次进入macro task queue,等待后续被调用,这些异步任务包括
一些异步任务的回调会依次进入micro task queue,等待后续被调用,这些异步任务包括:
【归纳总结】
console.log(1);
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(3)
});
});
new Promise((resolve, reject) => {
console.log(4)
resolve(5)
}).then((data) => {
console.log(data);
})
setTimeout(() => {
console.log(6);
})
console.log(7);
输出结果:1 4 7 5 2 3 6 整个流程为:
请戳:https://segmentfault.com/a/1190000004339133