事件
JavaScript与HTML的交互是通过事件实现的,事件代表文档或浏览器窗口中某个有意义的时刻。
可以使用仅在事件发生时执行的监听器(也称处理程序)订阅事件。
事件流
描述了页面接收事件的顺序。
事件冒泡
IE事件流被称为事件冒泡。 所有现代浏览器都支持事件冒泡。现代浏览器中的事件会一直冒泡到window对象。
事件捕获
意思是最不具体的节点应该最先收到事件,而最具体的节点应该收到事件。 实际上是为了在事件到达最终目标前拦截事件。
所有浏览器都是从window对象开始捕获事件。
由于旧版本浏览器不支持,因此实际当中几乎不会使用事件捕获,建议使用事件冒泡,特殊情况下可以使用事件捕获。
DOM事件流
DOM2 Events 规范规定事件流分为3个阶段:事件捕获、到达目标和事件冒泡。
事件捕获最先发生,为提前拦截事件提供了可能。然后,实际的目标元素接收到事件。最后一个阶段是冒泡,最迟要在这个阶段响应事件。
所有现代浏览器都支持DOM事件流,只有IE8以及更早版本不支持。
事件处理程序(或事件监听器)
即为响应事件而调用的函数。事件处理程序的名字以"on"开头。
HTML事件处理程序
作为事件处理程序执行的代码可以访问全局作用域中的一切。
大多数HTML事件处理程序会封装在try/catch块中。
<input type="button" value = "Click Me" onclick = "try{showMessage();}catch(ex) {}">
DOM0事件处理程序
在JS中指定事件处理程序的传统方式是把一个函数赋值给(DOM元素的)一个事件处理程序属性。
let btn = document.getElementById("myBtn");
btn.click = function(){
console.log("Clicked");
};
像这样使用DOM0方式为事件处理程序赋值时,所赋函数被视为元素的方法,因此,事件处理程序会在元素的作用域中运行,即this等于元素。
把事件处理程序设置为null,再点击按钮就不会执行任何操作了。
btn.onclick = null; // 移除事件处理程序
DOM2事件处理程序
为事件处理程序的赋值和移除定义了addEventListener()和 removeEventListener()方法, 它们接收3个参数:事件名、事件处理函数和一个布尔值,true标识在捕获阶段调用事件处理程序,false(默认值)表示在冒泡阶段调用事件处理程序。
let btn = document.getElementById("myBtn");
btn.addEventListener("click", () => {
console.log(this.id);
}, false);
使用DOM2方式的主要优势是可以为同一个事件添加多个事件处理程序。事件处理顺序是按照添加的顺序触发的。
通过addEventListener()添加的事件处理程序,只能使用removeEventListener()并传入与添加时同样的参数来移除。 意味着使用addEventListener()添加的匿名函数无法移除:
btn.removeEventListener("click", function() { // 没有效果
console.log(this.id);
}, false);
传给removeEventListener()的事件处理函数必须与传给addEventListener()的是同一个。
let btn = document.getElementById("myBtn");
let handler = function(){
console.log(this.id);
};
btn.addEventListener("click", handler, false);
btn.removeEventListener("click", handler, false); // 有效果
大多数情况下,事件处理程序会被添加到事件流的冒泡阶段,主要因为跨浏览器兼容性好。
把事件处理程序注册到捕获阶段,通常用于在事件到达其指定目标之前拦截事件,若不需要拦截,则不要使用事件捕获。
IE事件处理程序
为事件处理程序的赋值和移除定义了attachEvent()与detachEvent()方法,都接收两个同样的参数:事件处理程序的名字和事件处理函数。 因为IE8以及更早版本只支持事件冒泡,所以使用attachEvent()添加的事件处理程序会添加到冒泡阶段。
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
console.log("Clicked");
});
事件处理程序是在全局作用域中运行的,因此this等于window。与使用DOM0方式的this值不同。
attachEvent()也可以给一个元素添加多个事件处理程序,但是这里的事件触发程序会按照添加的顺序反向触发。
使用attachEvent()添加的事件处理程序将使用detachEvent()来移除,只要提供相同的参数。 与使用DOM方法类似,作为事件处理程序添加的匿名函数也无法移除。
跨浏览器事件处理程序
要确保事件处理代码具有最大兼容性,只需让代码在冒泡阶段运行即可。
为此,需要先创建一个addHandler()方法,此方法任务是根据需要分别使用DOM0方式、DOM2方式或IE方式来添加事件处理程序。 这个方法会在EventUtil对象(例子中使用的对象)上添加一个方法,以实现跨浏览器事件处理。接收3个参数:目标元素、事件名和事件处理函数。
removeHandler()同样接收3个参数,用于移除之前添加的事件处理程序,不管是通过哪种方式添加的,默认为DOM0方式。
事件对象
在DOM中发生事件时,所有相关信息都会被收集并存储在一个名为event的对象中。这个对象包含了一些基本信息,比如 导致事件的元素、发生的事件类型,以及可能与特定事件相关的任何其它数据。
DOM事件对象
在DOM合规的浏览器中,event对象是传给事件处理程序的唯一参数。不管以哪种方式(DOM0或DOM2)指定事件处理程序,都会传入这个event对象。
let btn = document.getElementById("myBtn");
btn.onclick = function(event) {
console.log(event.type); // "click"
};
btn.addEventListener("click", (event) => {
console.log(event.type); // "click"
}, false);
在通过HTML属性指定的事件处理程序中,同样可以使用变量event引用事件对象。
<input type = "button" value = "Click Me" onclick = "console.log(event.type)">
不同的事件生成的事件对象也会包含不同的属性和方法。不过,所有事件对象都会包含以下列出的这个公共属性和方法:
在事件处理程序内部,this对象始终等于currentTarget的值,而target只包含事件的实际目标。 如果事件处理程序直接添加在了意图的目标,则this、currentTarget和target的值是一样的。
type属性在一个处理程序处理多个事件时很有用,下面的处理程序就使用了event.type:
let btn = document.getElementById("myBtn");
let handler = function(event){
switch(event.type){
case "click":
console.log("clicked");
break;
case "mouseover":
event.target.style.backgroundColor = "red";
break;
case "mouseout":
event.target.style.backgroundColor = "";
break;
}
};
btn.onclick = handler;
btn.onmouseover = handler;
btn.onmouseout = handler;
preventDefault()用于阻止特定事件的默认动作。 如链接的默认行为就是在单击时导航到href属性指定的URL,若想阻止此行为,可以在onclick事件处理程序中取消。 任何可以通过preventDefault()取消默认行为的事件,其事件对象的cancelable属性都会设置为true。
stopPropagation()方法用于立即阻止事件流在DOM结构中传播,取消后续的事件捕获或冒泡。
如,直接添加到按钮的事件处理程序中调用此方法,可以阻止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");
};
若不调用stopPropagation(),则点击按钮会打印两条消息。由于click事件不会传播到document.body,因此onclick事件处理程序永远不会执行。
eventPhase属性用于确定事件流当前所处的阶段,若事件处理程序在捕获阶段被调用,则其值为1;若是在目标上被调用,则其值为2;若是在冒泡阶段被调用,则其值为3。 虽然“到达目标”是在冒泡阶段发生的,但其eventPhase仍然是2。
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
};
首先触发在捕获阶段的document.body上的事件处理程序,接着触发按钮本身的事件处理程序(尽管注册在冒泡阶段),最后触发注册在冒泡阶段的document.body上的事件处理程序。 event对象只在事件处理程序执行期间存在,一旦执行完毕,就会被销毁。
IE事件对象
与DOM事件对象不同,IE事件对象可以基于事件处理程序被指定的方式以不同方式来访问。
如果事件处理程序是使用DOM0方式指定的,则event对象只是window对象的一个属性。
btn.onclick = function(){
let event = window.event;
console.log(event.type); // "click"
};
如果事件处理程序是使用attachEvent()指定的,则event对象会作为唯一的参数传给处理函数:
btn.attachEvent("onclick", function(event) { console.log(event.type); // "click" });
与DOM事件对象一样,基于触发的事件类型不同,event对象中包含的属性和方法也不一样。所有IE事件对象都包含以下的公共属性和方法:
使用事件对象的srcElement属性代替this,不同事件对象上的srcElement属性中保存的都是事件目标:
btn.onclick = function(){
console.log(window.event.srcElement === this ); // true
};
btn.attachEvent("onclick", function(event) {
console.log(event.srcElement === this ); // false,由于事件处理程序运行在全局作用域下,因此不等
});
returnValue属性等价于DOM的preventDefault()方法,都用于取消给定时间的默认行为。只是这里要把returnValue设置为false才是阻止默认动作。
cancelBubble属性与DOM的stopPropagation()方法用途一样,都可以阻止事件冒泡。 由于IE8以及更早版本不支持捕获阶段,所以只会取消冒泡。而stopPropagation()既取消捕获也取消冒泡。
let btn = document.getElementById("myBtn");
btn.onclick = function(event){
console.log("Clicked");
window.event.cancelBubble = true;
};
document.body.onclick = function(event){
console.log("Body clicked");
};
可以阻止事件冒泡到document.body,也就阻止了调用注册在它上面的事件处理程序,因此点击按钮只会输出一条信息,即“Clicked”。
跨浏览器事件对象
DOM事件对象中包含IE事件对象的所有信息和能力,只是形式不同。这些共性可以让两种事件模型之间的映射成为可能。 前面提及的EventUtil对象可以添加一些方法:
var EventUtil = {
addHandler : function(element, type, handler){
// 之前的代码
},
getEvent : function(event){ // 返回对event对象的引用
return event ? event : window.event;
},
getTarget : function(event){ // 返回事件目标
return event.target || event.srcElement;
},
preventDefault : function(event){ // 阻止事件的默认行为
if(event.preventDefault){
event.preventDefault();
}else {
event.returnValue = false;
}
},
removeHandler : function(element, type, handler){
// 之前的代码
},
stopPropagation : function (event){ // 停止事件流的DOM方法
if(event.stopPropagation){
event.stopPropagation();
}else {
event.cancelBubble= true;
}
}
};
事件类型
用户界面事件
用户界面事件或UI事件不一定跟用户操作有关。
UI事件主要有以下几种:
1. load事件
可能是JavaScript中最常用的事件。在window对象上,load事件会在整个页面(包括所有外部资源如图片、JavaScript文件和CSS文件)加载完成后触发。
可通过两种方式指定load事件处理程序:
- JavaScrip方式【开发时尽量使用此方式】
window.addEventListener("load", (event) => {
console.log("Loaded!!");
});
- 向<body>元素添加onload属性
<!DOCTYPE html>
<html>
<head>
<title>Load Event Example</title>
</head>
<body onload = "console.log('Loaded!')">
</body>
</html>
图片也会触发load事件,包括DOM和非DOM中的图片。
可以在HTML中直接给/元素的onload属性指定事件处理程序:
<img src = "cln.gif" onload = "console.log('Loaded!')">
同样,也可以使用JavaScript为图片指定事件处理程序。 在通过JS创建新<img>元素时,也可以给这个元素指定一个在加载完成后执行的事件处理程序,关键是在赋值src属性前指定事件处理程序。
window.addEventListener("load", () => {
let image = document.createElement("img");
image.addEventListener("load", (event) =>{
console.log(event.target.src);
});
document.body.appendChild(image);
image.src = "cln.gif";
});
下载图片并不一定要把<img> 元素添加到文档,只要把它设置了src属性就会立即下载。
同样的技术也适用于DOM0的Image对象。在DOM出现之前,客户端都使用Image对象预先加载照片。
可以像使用前面(使用createElement()方法创建)的/元素一样使用Image对象,只是不能把后面添加到DOM树。
window.addEventListener("load", () => {
let image = new Image();
image.addEventListener("load", (event) =>{
console.log("Image loaded!!");
});
image.src = "cln.gif";
});
<script>元素会在JS文件加载完成后触发load事件,从而可以动态检测。 与图片不同,要下载JS文件必须同时指定src属性并把<script>元素添加到文档中。
window.addEventListener("load", () => {
let script = document.createElement("script");
script.addEventListener("load", (event) =>{
console.log("Loaded");
});
script.src = "example.js";
document.body.appendChild(script);
});
IE8及更早版本不支持<script>元素触发load事件。
IE和Opera支持<link> 元素触发load事件,因而支持动态检测样式表是否加载完成。
window.addEventListener("load", () => {
let link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
link.addEventListener("load", (event) =>{
console.log("css loaded");
});
link.href = "example.css";
document.getElementByTagName("head")[0].appendChild(link);
});
与<script>节点一样,在指定href属性并把<link>节点添加到文档之前不会下载样式表。
2. unload事件
会在文档卸载完成后触发。unload事件一般是在从一个页面导航到另一个页面时触发,最常用于清理引用,以避免内存泄漏。
与load事件类似,unload事件处理程序也有两种指定方式:
- JavaScrip方式
window.addEventListener("unload", (event) => {
console.log("Unloaded!!");
});
这种事件生成的event对象在DOM合规的浏览器中只有target属性(值为document)。IE8及更早版本在这个事件上不提供srcElement属性。
- 给<body>元素添加onunload属性
<!DOCTYPE html>
<html>
<head>
<title>Unload Event Example</title>
</head>
<body onunload = "console.log('Unloaded!')">
</body>
</html>
3. resize事件
当浏览器窗口被缩放到新高度或宽度时,会触发resize事件。这个事件在window上触发。 因此可以通过JavaScript在window上,或者为<body>元素添加onresize属性来指定事件处理程序。 优先使用JavaScript方式:
window.addEventListener("resize", (event) => {
console.log("Resized!!");
});
浏览器窗口在最大化和最小化时也会触发resize事件。
焦点事件
在页面元素获得或失去焦点时触发。 焦点事件有以下6种:
- blur:当元素失去焦点时触发。这个事件不冒泡,所有浏览器都支持。
- DOMFocusIn:当元素获取焦点时触发。是focus的冒泡版,只有Opera唯一支持此事件。DOM3 Event废弃此事件,推荐focusin。
- DOMFocusOut:当元素失去焦点时触发。是blur的通用版,只有Opera唯一支持此事件。DOM3 Event废弃此事件,推荐focusout。
- focus:当元素获取焦点时触发。这个事件不冒泡,所有浏览器都支持。
- focusin:当元素获取焦点时触发。是focus的冒泡版。
- focusout:当元素失去焦点时触发。是blur的通用版。
焦点事件中的两个主要事件是focus和blur,在JS早期就得到了浏览器支持。
鼠标和滚轮事件
DOM3 Events定义的9种鼠标事件:
- click:在用户单击鼠标主键(通常是左键)或按键盘回车时触发。
- dbclick:在用户双击鼠标主键(通常是左键)时触发。
- mousedown:在用户按下任意鼠标键时触发。
- mouseenter:在用户把鼠标光标从元素外部移到元素内部时触发。这个事件不冒泡,也不会在光标经过后代元素时触发。
- mouseleave:在用户把鼠标光标从元素内部移到元素外部时触发。这个事件不冒泡,也不会在光标经过后代元素时触发。
- mousemove:在鼠标光标在元素上移动时反复触发。
- mouseout:在用户把鼠标光标从一个元素移到另一个元素上时触发。移到的元素可以是原始元素的外部元素,也可以是原始元素的子元素。
- mouseover:在用户把鼠标光标从元素外部移到元素内部时触发。
- mouseup:在用户释放鼠标键时触发。
页面中的所有元素都支持鼠标事件。
鼠标事件还有一个名为滚轮事件的子类别,只有一个事件mousewheel,反映的时鼠标滚轮或带滚轮的类似设备上滚轮的交互。
- 客户端坐标 鼠标事件都是在浏览器视口中的某个位置上发生的。这些信息被保存在event对象的clientX和clientY属性中,这两个属性表示事件发生时鼠标光标在视口中的坐标,所有浏览器都支持。
div.addEventListener("click", (event) => {
console.log('Client coordinates : ${event.clientX}, ${event.clientY}');
});
客户端坐标不考虑页面滚动,因此这两个值并不代表鼠标在页面上的位置。
- 页面坐标 客户端坐标是事件发生时鼠标光标在客户端视口中的坐标,而页面坐标是事件发生时鼠标光标在页面上的坐标。 通过event对象的pageX和pageY可以获取,这两个属性表示鼠标光标在页面上的位置,因此反映的是光标到页面,而非视口左边与上边的距离。
div.addEventListener("click", (event) => {
console.log('Page coordinates : ${event.pageX}, ${event.pageY}');
});
在页面没有滚动时,pageX和pageY与clientX和clientY的值相同。
- 屏幕坐标 可以通过event对象的screenX和screenY属性获取鼠标光标在屏幕上的坐标。
div.addEventListener("click", (event) => {
console.log('Screen coordinates : ${event.screenX}, ${event.screenY}');
});
- 修饰键 键盘上的修饰键Shift、Ctrl、Alt和Meta,经常用于修改鼠标事件的行为。
DOM规定了4个属性来表示这几个修饰键的状态:shiftKey、ctrlKey、altKey和metaKey,会在各自对应的修饰键被按下时包含布尔值true,没有被按下时包含false。
现代浏览器支持所有四个修饰键。IE8及更早版本不支持metaKey属性。
- 相关元素 对mouseover事件来说,事件的主要目标是获得光标的元素,相关元素是失去光标的元素; 对mouseout事件来说,事件的主要目标是失去光标的元素,相关元素是获得光标的元素;
DOM通过event对象的relatedTarget属性提供相关元素的信息,这个属性只有在mouseover和mouseout事件发生时才包含值,其它所有事件的这个属性的值都是null。
- 鼠标按键 对mousedown和mouseup事件来说,event对象上会有一个button属性,表示按下或释放的时哪个按键。 DOM为这个button属性定义了3个值:
- 0表示鼠标主键,
- 1表示鼠标中键(通常也是滚轮键),
- 2表示鼠标副键。 按照惯例,鼠标主键通常时左边的按键,副键通常是右边的按键。
- 额外事件信息 DOM2 Event规范在event对象上提供了detail属性,以给出关于事件的更多信息。
对鼠标事件来说,detail包含一个数值,表示在给定位置上发生了多少次单击。 detail的值从1开始,每次单击会加1。 如果鼠标在mousedown和mouseup之间移动了,则detail会重置为0。
- mousewheel事件 会在用户使用鼠标滚轮时触发,包括在垂直方向上任意滚动。 这个事件会在任何元素上触发,并冒泡到(在IE8中)document和(在所有现代浏览器中)window。
此事件的event对象包含鼠标事件的所有标准信息,此外还有一个名为wheelDelta的新属性, 当鼠标滚轮向前滚动时,wheelDelta每次都是+120;而鼠标滚轮向后滚动时,wheelDelta每次都是-120。
可以为页面上的任何元素或文档添加onmousewheel事件处理程序,以处理所有鼠标滚轮交互。
document.addEventListener("onmousewheel", (event) =>{
console.log(event.wheelDelta);
});
多数情况下只需知道滚轮滚动的方向,可以通过wheelDelta值的符号知道。
键盘与输入事件
键盘事件包含3个事件:
- keydown,用户按下键盘上某个键时触发,而且持续按住会重复触发。
- keypress,用户按下键盘上某个键并产生字符时触发,而且持续按住会重复触发。Esc键也会触发这个事件。Dom3 Events废弃了keypress事件,而推荐textInput事件。
- keyup,用户释放键盘上某个键时触发。
输入事件只有一个,即textInput。是对keypress事件的扩展,用于在文本显示给用户之前更方便地截获文本输入。textInput会在文本被插入到文本框之前触发。
当用户按下某个字符键时,首先触发keydown事件,然后触发keypress事件,最后触发keyup事件。前两个事件会在文本框出现变化之前触发,而keyup会在变化之后触发。 键盘事件支持与鼠标事件相同的修饰键。
- 键码 对于keydown和keyup事件,event对象的keyCode属性中会保存一个键码,对应键盘上特定的一个键。 对于字母和数字键,keyCode的值与小写字母和数字的ASCII编码一致。 如数字7键的keyCode为55,而字母A键的keyCode为65,而且跟是否按了Shift键无关。 键盘上所有非字符键的键码:
2. 字符编码
对插入或移除字符的键,所有浏览器都会触发keypress事件,其他键取决于浏览器。
浏览器在event对象上支持charCode属性,只有发生keypress事件时这个属性才会被设置值。包含的是按键字符对应的ASCII编码。通常charCode属性的值为0,在keypress事件发生时则是对应按键键码。
- DOM3的变化 DOM3 Events规范并未规定charCode属性,而是定义了key和char两个新属性。
key属性用于替代keyCode,且包含字符串。在按下字符键时,key的值等于文本字符; 在按下非字符键时,key的值时键名。
char属性在按下字符键时与key类似,在按下非字符键时为null。
IE只支持key属性。Safari和Chrome支持keyIdentifier属性,在按下非字符键时返回与key一样的值,对于字符键,keyIdentifier返回以“U+0000”形式表示Unicode值的字符串形式的字符编码。
由于缺乏跨浏览器支持,不建议使用key、keyIdentifier和char。
DOM3 Events支持一个名为location属性,该属性是一个数值,表示在哪里按的键。 可能的值:0—默认键,1—左边,2—右边,3—数字键盘,4—移动设备,5—游戏手柄。 与key属性类似,location属性没得到广泛支持,不建议在跨浏览器开发时使用。
最后一个变化时给event对象增加了getMpdifierState()方法,接收一个参数,一个等于Shift、Control、Alt、AltGraph或Meta的字符串,表示要检测的修饰键。 若给定的修饰键处于激活状态(即键被按住),则方法返回true、否则返回false。
- textInput事件 DOM3 Events规范增加了一个名为textInput的事件,其在字符被输入到可编辑区域时触发。
作为对keypress的替代,此事件的行为有些不一样。
区别一:keypress会在任何可以获得焦点的元素上触发,而textInput只在可编辑区域上触发。
区别二:textInput只在有新字符被插入时才会触发,而keypress对任何可能影响文本的键都会触发(包括退格键)。
由于此事件主要关注字符,所以在event对象上提供了一个data属性,包含要插入的字符(不是字符编码)。 data的值始终是要被插入的字符,因此如果在按S键时没按Shift键,data的值为“s”;但在按S键同时按Shift键,data的值则是“S”。
event对象上还有一个名为inputMethod属性,该属性表示向控件中输入文本的手段,可能的值如下: 0,表示浏览器无法确定什么输入手段; 1,表示键盘; 2,表示粘贴; 3,表示拖放操作; 4,表示IME; 5,表示表单选项; 6,表示手写; 7,表示语音; 8,表示组合方式; 9,表示脚本。
合成事件【DOM3 Event新增】
用于处理通常使用IME(即输入法编辑器)输入时的复杂输入序列。 IME通常需要同时按下多个键才能输入一个字符,合成事件用于检测和控制这种输入。 合成事件有以下3种:
- compositionstart,在IME的文本合成系统打开时触发,表示输入即将开始;
- compositionupdate,在新字符插入输入字段时触发;
- compositionend,在IME的文本合成系统关闭时触发,表示恢复正常键盘输入。
在合成事件触发时,事件目标是接收文本的输入字段。唯一增加的事件属性是data,其中包含的值视情况而异:
- 在compositionstart事件中,包含正在编辑的文本;
- 在compositionupdate事件中,包含要插入的新字符;
- 在compositionend事件中,包含本次合成过程中输入的全部内容。
HTML5事件
1.contextmenu事件
用于表示何时该显示上下文菜单。 实现上下文菜单功能的JS代码:
2.beforeunload事件
此事件会在window上触发,用意是给开发者提供阻止页面被卸载的机会。 会在页面即将从浏览器中卸载时触发,若页面需要继续使用,则可以不卸载。 相反,这个事件会向用户显示一个确认框,询问用户确认是希望关闭页面,还是继续留在页面上。
内存与性能
在JS中,页面中事件处理程序的数量与页面整体性能直接相关。
原因:首先,每个函数都是对象,都占有内存空间,对象越多,性能越差。 其次,为指定事件处理程序所需访问DOM的次数增多会造成整个页面交互的延迟。
事件委托
是"过多事件处理程序"的解决方案。事件委托利用事件冒泡,可以只使用一个事件处理程序来管理一种类型的事件。
例如,click事件冒泡到document,意味着可以为整个页面指定一个onclick事件处理程序,而不用为每个可点击元素分别指定事件处理程序。
<body>
<ul id="myLinks">
<li id="goSomewhere">Go Somewhere</li>
<li id="doSomething">Do Something</li>
<li id="sayHi">sayHi</li>
</ul>
<script type="text/javascript">
let list = document.getElementById("myLinks");
list.addEventListener("click", (event) =>{
let target = event.target;
switch(target.id){
case "doSomething" :
document.title = "I changed";
break;
case "goSomewhere" :
location.href = "http://www.baidu.com";
break;
case "sayHi" :
console.log("hi");
break;
}
});
</script>
</body>
这里给<ul>元素添加了一个onclick事件处理程序,因为所有列表项都是这个元素的后代,所以它们的事件都会向上冒泡,最终都会由这个函数来处理。
所有使用按钮的事件(大多数鼠标实际和键盘事件)都适用于这个解决方案。
相对于之前的技术,事件委托具有以下优点:
- document对象随时可用,任何时候都可以给它添加事件处理程序。意味着只要页面渲染出可点击的元素,就可以无延迟地起作用。
- 只指定一个事件处理程序,既可以节省DOM引用,也可节省时间。
- 减少整个页面所需的内存,提升整体性能。
最适合使用事件委托的事件包括: click、mousedown、mouseup、keydown和keypress。
删除事件处理程序
除了通过事件委托,来限制浏览器的代码和负责页面交互的JS代码之间的连接之外,还应该及时删除不用的事件处理程序。 很多Web应用性能不佳都是由于无用的事件处理程序长驻内存导致的。
导致这种问题的主要原因:
- 删除带有事件处理程序的元素 如通过真正的DOM方法removeChild()和replaceChild()删除节点,最常见的还是使用innerHTML整体替换页面的某一部分。 此时,被innerHTML删除元素上,如果还有事件处理程序,就不会被垃圾收集程序正常清理。
如果知道某个元素会被删除,最好在删除它之前,手动删除它的事件处理程序。
在事件处理程序中删除按钮,会阻止事件冒泡。只有事件目标仍然存在于文档中时,事件才会冒泡。
- 页面卸载 如果在页面卸载后,事件处理程序没有被清理,则它们仍然会残留在内存中。
一般来说,最好在onunload事件处理程序中趁页面尚未卸载,先删除所有事件处理程序。
模拟事件
DOM事件模拟
任何时候,都可以使用document.createEvent()方法创建一个event对象,接收一个参数,是一个表示要创建事件类型的字符串。 可用的字符串值有:
- “UIEvents”(DOM3中是UIEvent)—通用用户界面事件、
- “MouseEvents”(DOM3中是MouseEvent)—通用鼠标事件和
- “HTMLEvents”(DOM3中没有)—通用HTML事件
创建event对象之后,需要使用事件相关的信息来初始化。
事件模拟的最后一步是触发事件,因此使用dispatchEvent()方法,存在于所有支持的DOM节点值上。dispatchEvent()接收一个参数,即表示要触发事件的event对象。
- 模拟鼠标事件 首先创建鼠标event对象,可以调用createEvent()方法并传入“MouseEvents”参数。这样就返回event对象,此对象有一个initMouseEvent()方法,用于为新对象指定鼠标的特定信息,接收15个参数,分别对应鼠标事件会暴露的属性。 参数如下:
前四个参数是正确模拟事件的唯一重要的几个参数,是浏览器要用的,其他参数是事件处理程序要用的。
使用默认值模拟单击事件:
let btn = document.getElementById("myBtn");
let event = document.createEvent("MouseEvents"); // 创建event对象
event.initMouseEvent("click", true, true, document.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null); // 初始化event对象
btn.dispatchEvent(event); // 触发事件
- 模拟键盘事件 在DOM3中创建键盘事件的方式是给createEvent()方法传入参数“KeyboardEvent”,返回的event对象有一个initKeyboardEvent()方法,接收以下参数:
在使用document.createEvent(“KeyboardEvent”)之前,最好检测一下浏览器对DOM3键盘事件的支持情况,其它浏览器会返回非标准的KeyboardEvent对象。
Firefox允许给createEvent()方法传入参数“KeyEvent”来创建键盘事件,此时返回event对象包含的方法initKeyEvent(),接收以下参数:
键盘事件也可以通过调用dispatchEvent()并传入event对象来触发。
- 自定义DOM事件 DOM3增加了自定义事件的类型。要创建自定义事件,需要调用createEvent(“CustomEvent”),返回的对象包含initCustomEvent()方法,接收以下参数:
IE事件模拟
首先,要使用document对象的createEventObject()方法来参加event对象,此方法不接受参数。 返回的event对象,可以手工给此对象指定希望对象具备的所有属性。 最后在事件目标上调用fireEvent()方法,接收两个参数:事件处理程序的名字和event对象。 调用fireEvent()时,srcElement和type属性会自动指派到event对象,意味着IE支持的所有事件都可通过相同的方式来模拟。
在一个按钮上模拟了click事件:
var btn = document.getElementById("myBtn");
var event = document.createEventObject(); // 创建event对象
event.screenX = 100; // 初始化event对象
event.screenY = 0;
event.clientX = 0;
event.clientY = 0;
event.ctrlKey = false;
event.altKey = false;
event.shiftKey = false;
event.button = 0;
btn.fireEvent("onclick", event); // 触发事件