JavaScript 与 HTML 之间的交互是通过事件实现的。
一、事件流
事件流描述的是从页面中接收事件的顺序。IE的事件流是事件冒泡流,而Netscape Communicator 的事件流是事件捕获流。
(1)事件冒泡
IE 的事件流叫做事件冒泡,即事件开始时由最具体的元素 (文档中嵌套层次最深的那个节点) 接收,然后逐级向上传播到较为不具体的节点(文档)。
比如单击了页面中的<div>元素,那么这个click事件会按照如下顺序逐级向上传播,直至传播到 document 对象。
注意:所有现代浏览器都支持事件冒泡,但在具体实现上还是有一些差别。IE5.5及更早版本中的事件冒泡会跳过 <html> 元素 (从 <body> 直接跳到 document) 。Firefox、Chrome 和 Safari 则将事件一直冒泡到 window 对象
(2)事件捕获
事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。事件捕获的用意在于在事件到达预定目标之前捕获它。
前面的例子的事件捕获过程为:
(3)DOM事件流
“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。
同样前面的例子触发事件的顺序为:
二、事件处理程序
事件就是用户或浏览器自身执行的某种动作。而响应某个事件的函数就叫做事件处理程序 (或事件侦听器)。事件处理程序的名字以 "on" 开头,比如 click 事件的事件处理程序就是 onclick。为事件指定处理程序的方式有以下几种。
(1)HTML事件处理程序
在HTML中定义的事件处理程序可以包含要执行的具体动作,也可以调用在页面其他地方定义的脚本。
<input type="button" value="Click Me" onclick="alert('Clicked')" />
或者<script type="text/javascript">
function showMessage(){
alert("Hello world!");
}
</script>
<input type="button" value="Click Me" onclick="showMessage()" />
关于动态创建的函数,另一个有意思的地方是它扩展作用域的方式。function () {
with(document) {
with(this) {
//元素属性值
}
}
}
<input type="button" value="Click Me" onclick="alert(value)">
如果当前元素是一个表单输入元素,则作用域中还会包含访问表单元素的入口。
function () {
with(document) {
with(this.form) {
with(this){
//元素属性值
}
}
}
}
<form method="post">
<input type="text" name="username" value="">
<input type="button" value="Echo Username" onclick="alert(username.value)">
</form>
(2)DOM0级事件处理程序
通过 JavaScript 指定事件处理程序的传统方式,就是将一个函数赋值给一个事件处理程序属性。这种为事件处理程序赋值的方法是在第四代Web浏览器中出现的,而且至今仍然为所有现代浏览器所支持。原因一是简单,二是具有跨浏览器的优势。
(3)DOM2级事件处理程序
“DOM2级事件”定义了两个方法,addEventListener() 和 removeEventListener(),它们都接受3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是 true,表示在捕获阶段调用事件处理程序;如果是 false,表示在冒泡阶段调用事件处理程序。
使用 DOM2 级方法添加事件处理程序的主要好处是可以添加多个事件处理程序。
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
alert(this.id);
}, false);
var handler = function(){
alert(this.id);
};
btn.addEventListener("click", handler, false);
// 这里省略了其他代码
btn.removeEventListener("click", handler, false); // 有效!
(4)IE事件处理程序
IE 实现了与DOM中类似的两个方法:attachEvent() 和 detachEvent()。这两个方法接受相同的两个参数:事件处理程序名称与事件处理程序函数。由于IE只支持事件冒泡,所以通过 attachEvent() 添加的事件处理程序都会被添加到冒泡阶段。
在使用DOM0级方法的情况下,事件处理程序会在其所属元素的作用域内运行;在使用 attachEvent() 方法的情况下,事件处理程序会在全局作用域中运行,因此 this 等于 window 。并且,与DOM 方法不同的是,这些事件处理程序不是以添加它们的顺序执行,而是以相反的顺序被触发。
var btn = document.getElementById("myBtn");
var handler = function(){
alert("Clicked");
};
btn.attachEvent("onclick", handler);
// 这里省略了其他代码
btn.detachEvent("onclick", handler);
(5)跨浏览器的事件处理程序
第一个要创建的方法是 addHandler(),它的职责是视情况分别使用DOM0级方法、DOM2级方法或IE方法来添加事件,它属于一个名叫 EventUtil 的对象。addHandler()方法接受3个参数:要操作的元素、事件名称和事件处理程序函数。
EventUtil的用法如下所示:
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;
}
}
};
使用EventUtil对象的实例如下:var btn = document.getElementById("myBtn");
var handler = function(){
alert("Clicked");
};
EventUtil.addHandler(btn, "click", handler);
// 这里省略了其他代码
EventUtil.removeHandler(btn, "click", handler);
三、事件对象
在触发 DOM 上的某个事件时,会产生一个事件对象 event,这个对象中包含着所有与事件有关的信息。
(1)DOM中的事件对象
兼容 DOM 的浏览器会将一个 event 对象传入到事件处理程序中。
event 对象包含与创建它的特定事件有关的属性和方法。触发的事件类型不一样,可用的属性和方法也不一样。不过,所有事件都会有下表列出的成员。
在事件处理程序内部,对象 this 始终等于 currentTarget 的值,而 target 则只包含事件的实际目标。如果直接将事件处理程序指定给了目标元素,则 this 、currentTarget 和 target 包含相同的值。
(2)IE中的事件对象
在使用 DOM0 级方法添加事件处理程序时,event 对象作为 window 对象的一个属性存在。如果是通过 HTML 特性指定的事件处理程序,那么还可以通过一个名叫 event 的变量来访问 event 对象 (与 DOM 中的事件模型相同)。
IE 的 event 对象同样也包含与创建它的事件相关的属性和方法。
(3)跨浏览器的事件对象
var EventUtil = {
addHandler: function(element, type, handler){
// 省略了其他代码
},
getEvent: function(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){
if (event.stopPropagation){
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
};
四、事件类型
Web浏览器中可能发生的事件有很多类型。如前所述,不同的事件类型具有不同的信息,而 "DOM3级事件" 规定了下列几类事件:
- UI事件:在用户与页面上的元素交互时触发;
- 鼠标事件:当用户通过鼠标在页面上执行操作时触发;
- 滚轮事件:当使用鼠标滚轮(或类似设备)时触发;
- 文本事件:当在文档中输入文本时触发;
- 键盘事件:当用户通过键盘在页面上执行操作时触发;
- 合成事件:当为IME(Input Method Editor,输入法编辑器)输入字符时触发;
- 变动 (mutation) 事件:当底层 DOM 结构发生变化时触发。
1.load事件
EventUtil.addHandler(window, "load", function(event){
alert("loaded!");
});
2.unload事件
3.resize事件
注意:关于何时会触发resize事件,不同浏览器有不同的机制。IE、Safari、Chrome和Opera会在浏览器窗口变化了1像素时就触发resize事件,然后随着变化不断重复触发。Firefox则只会在用户停止调整窗口大小时才会触发resize事件。
4.scroll事件
(2)焦点事件
焦点事件会在页面元素获得或失去焦点时触发,利用这些事件并与document.hasFocus()方法及document.activeElement属性配合,可以知晓用户在页面上的行踪。有以下6个焦点事件:blur、DOMFocusIn、DOMFocusOut、foucs、focusin、focusout。
(3)鼠标与滚轮事件
DOM3级事件定义了9个鼠标事件:click、dblclick、mousedown、mouseenter、mouseleave、mousemove、mouseout、mouseover、mouseup。
鼠标事件中还有一类滚轮事件mousewheel事件。
1.客户区坐标位置:clientX和clientY属性。
2.页面坐标位置:pageX和pageY属性。
3.屏幕坐标位置:screenX和screenY属性。
4.修改键:shiftKey、ctrlKey、altKey、metaKey。
5.相关元素:DOM通过event对象的relatedTarget属性提供了相关元素的信息。而在IE中,mouseover事件触发时,fromElement保存了相关元素,mouseout事件触发时,toElement保存了相关元素。
6.鼠标按钮
7.更多的事件信息
8.鼠标滚轮事件:mousewheel事件中包含wheelDelta属性,Firefox支持类似的DOMMouseScroll事件,包含detail属性。
(4)键盘与文本事件
DOM3级含有3个键盘事件:keydown、keypress、keyup。
1.键码:keyCode属性
2.字符编码:charCode属性
3.DOM3级变化:新属性(key、char、location属性:表示按下了什么位置上的键),getModifierState()方法。
4.textinput事件:data属性:表示用户输入的字符(而非字符编码)、inputMethod属性:表示把文本输入到文本框中的方式。
(5)复合事件:用以处理IME的输入序列的一类事件。
(6)变动事件:删除节点、插入节点等。
(7)HTML5事件
1.contextmenu事件:用以表示何时应该显示上下文菜单。
2.beforeunload事件:让开发人员有可能在页面卸载前阻止这一操作。
3.DOMContentLoaded事件:在形成完整的DOM树之后触发。
4.readystatechange事件:提供与文档或元素的加载状态有关的信息。
5.pageshow和pagehide事件:pageshow事件在页面显示时触发,pagehide事件在浏览器卸载页面时触发。
6.hashchange事件:以便在URL的参数列表发生变化时通知开发人员。
(8)设备事件
1.orientationchange事件
2.MozOrientation事件
3.deviceorientation事件
4.devicemotion事件
(9)触摸与手势事件
1.触摸事件
2.手势事件
五、内存与性能
在 JavaScript 中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能。导致这一问题的原因是多方面的。首先,每个函数都是对象,都会占用内存;内存中的对象越多,性能就越差。其次,必须事先指定所有事件处理程序而导致的 DOM 访问次数,会延迟整个页面的交互就绪时间。
(1)事件委托
对 "事件处理程序过多" 问题的解决方案就是事件委托。事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
(2)移除事件处理程序
六、模拟事件
(1)DOM中的事件模拟
- 使用createEvent()方法创建event对象,接收一个参数,即表示要创建的事件类型的字符串(UIEvents、MouseEvents、MutationEvents、HTMLEvents等)。
- 使用与事件有关的信息对其进行初始化。
- 触发事件,使用dispatchEvent()方法,接收一个参数,即表示要触发事件的event对象。
var btn = document.getElementById("myBtn");
//创建事件对象
var event = document.createEvent("MouseEvents");
//初始化事件对象
event.initMouseEvent("click", true, true, document.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
//触发事件
btn.dispatchEvent(event);
2.模拟键盘事件:“KeyboardEvent”事件类型字符串,initKeyEvent()方法
3.模拟其它事件
4.自定义DOM事件:createEvent("CustomEvent")创建事件,initCustomEvent()方法初始化。
(2)IE中的事件模拟
- 调用document.createEventObject()方法创建event对象。
- 手工为对象添加所有必要的信息。
- 在目标上调用fireEvent()方法,接收两个参数:事件处理程序的名称和event对象。