一、事件流
定义:事件流描述的是从页面中接收事件的顺序。
但有意思的是,IE 和Netscape 开发团队居然提出了差不多是完全相反的事件流的概念。IE 的事件流是事件冒泡流,而Netscape Communicator 的事件流是事件捕获流。
1、事件冒泡(event-bubbling)
IE 的事件流叫做事件冒泡(event bubbling),即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。
Click Me
如上代码,如果点击了id位myDIv的元素后,之后会按照如图传播
2、事件捕获(event-capturing)
事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。事件捕获的用意在于在事件到达预定目标之前捕获它。
由于老版本的浏览器不支持, 因此很少有人使用事件捕获。我们也建议读者放心地使用事件冒泡,在有特殊需要时再使用事件捕获。
3、DOM事件流
“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。
在DOM 事件流中,实际的目标(
多数支持DOM 事件流的浏览器都实现了一种特定的行为;即使“DOM2 级事件”规范明确要求捕获阶段不会涉及事件目标,但IE9、Safari、Chrome、Firefox 和Opera 9.5 及更高版本都会在捕获阶段触发事件对象上的事件。结果, 就是有两个机会在目标对象上面操作事件。
二、事件处理程序
我们先来了解几个概念
事件:事件就是用户或浏览器自身执行的某种动作。诸如click、load 和mouseover,都是事件的名字
事件处理程序:响应某个事件的函数就叫做事件处理程序(或事件侦听器)。事件处理程序的名字以"on"开头,因此click 事件的事件处理程序就是onclick,load 事件的事件处理程序就是onload。为事件指定处理程序的方式有好几种
1、HTML事件处理程序
某个元素支持的每种事件,都可以使用一个与相应事件处理程序同名的HTML 特性来指定。这个特性的值应该是能够执行的JavaScript 代码
特点:
- 会创建一个封装着元素属性值的函数。这个函数中有一个局部变量event,也就是事件对象。通过event 变量,可以直接访问事件对象,你不用自己定义它,也不用从函数的参数列表中读取。
- 在这个函数内部,this 值等于事件的目标元素
2、DOM0 级事件处理程序
简单的说:就是将一个函数赋值给一个事件处理程序属性。例如:
var btn = document.getElementById("myBtn");
btn.onclick = function() {
alert("Clicked");
}
使用DOM0 级方法指定的事件处理程序被认为是元素的方法。因此,这时候的事件处理程序是在元素的作用域中运行;换句话说,程序中的this 引用当前元素。
var btn = document.getElementById("myBtn");
btn.onclick = function() {
alert(this.id);
}
以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。
也可以删除通过DOM0 级方法指定的事件处理程序,只要像下面这样将事件处理程序属性的值设置为null 即可
btn.onclick = null;
DOM0 级对每个事件只支持一个事件处理程序。
2、DOM2级事件处理程序
“DOM2级事件”定义了两个方法,用于处理指定和删除事件处理程序的操作:addEventListener()和removeEventListener()。所有DOM节点中都包含这两个方法,并且它们都接受3 个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。
btn.addEventListener("click",function(){
alert(this.id);
},false);
通过addEventListener()添加的事件处理程序只能使用removeEventListener()来移除;移除时传入的参数与添加处理程序时使用的参数相同。这也意味着通过addEventListener()添加的匿名函数将无法移除
var btn = document.getElementById("myBtn");
var handler = function() {
alert(this.id);
};
btn.addEventListener("click", handler, false);
btn.removeEventListener("click", handler, false);
IE9、Firefox、Safari、Chrome 和Opera 支持DOM2 级事件处理程序。
3、IE事件处理程序
attchEvent(eventType, function) detachEvent(eventType, function)
在使用DOM0 级方法的情况下,事件处理程序会在其所属元素的作用域内运行;在使用attachEvent()方法的情况下,事件处理程序会在全局作用域中运行,因此this 等于window。
btn.attachEvent("oncick", function(){
alert(this === window);
});
三、事件对象
在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事件有关的信息。包括导致事件的元素、事件的类型以及其他与特定事件相关的信息。
1、DOM中的事件对象
兼容DOM 的浏览器会将一个event 对象传入到事件处理程序中。无论指定事件处理程序时使用什么方法(DOM0 级或DOM2 级),都会传入event 对象。
var btn = document.getElementById("myBtn");
btn.onclick = function(){
alert(event.type); // click
}
btn.addEventListener("click",function(event){
alert(event.type); // click
},false);
var btn = document.getElementById("myBtn");
document.body.onclick = function() {
alert(event.currentTarget === document.body); //true
alert(this === document.body); //true
alert(event.target === document.getElementById("myBtn")); //true
}
preventDefault()
阻止特定事件的默认行为
var link = document.getElementById("myLink");
link.onclick = function(event){
event.preventDefault();
}
stopPropagation()
立即停止事件在DOM层次的传播,即取消进一步的事件捕获或冒泡
btn.onclick = function(event) {
alert("clicked");
event.stopPropagation();
};
document.body.onclick = function(event) {
alert("body clicked");
}
eventPhase属性
确定事件当前正位于事件流的哪个阶段
捕获阶段:1
目标对象上:2
冒泡阶段:3
btn.onclick = function(event) {
alert(event.eventPhase); //2
};
document.body.addEventListener("click",
function(event){
alert(event.eventPhase); //1
},true);
document.body.onclick = function(event){
alert(event.eventPhase); //3
}
**注意:只有在事件处理程序执行期间,event 对象才会存在;一旦事件处理程序执行完成,event 对象就会被销毁。 **
2、IE中的事件对象
在使用DOM0 级方法添加事件处理程序时,event 对象作为window 对象的一个属性存在
attachEvent()添加的,那么就会有一个event 对象作为参数被传入事件处理程序函数中
3、兼容性的事件对象
var EventUtil = {
addHandler:function(element, type, handler){
if(element.addEventListener){
element.addEventListener(type, handler, false);
}else if(element.attchEvent) {
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;
}
},
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;
}
},
stopPropagation: function(event) {
if(event.stopPropagation) {
event.stopPropagation();
}else{
event.cancelBubble = true;
}
}
};
四、事件类型
1、UI事件
load
当页面完全加载后(包括所有图像、JavaScript 文件、CSS 文件等外部资源),就会触发window 上面的load 事件。
unload
与load 事件对应的是unload 事件,这个事件在文档被完全卸载后触发。只要用户从一个页面切换到另一个页面,就会发生unload 事件。而利用这个事件最多的情况是清除引用,以避免内存泄漏。
resize
当浏览器窗口被调整到一个新的高度或宽度时,就会触发resize 事件。
EventUtil.addHandler(window, "resize", function(event){
alert("Resized");
});
scroll
2、焦点事件
focus
在元素获得焦点时触发
blur
在元素失去焦点时触发
3、鼠标和滚轮事件
click
dblclick
mousedown
mouseup
mouseenter
mouseleave
mouseover
mouseout
- 客户区坐标位置
var div = document.getElementById('myDiv');
EventUtil.addHandler(div, "click", function(event){
event = EventUtil.getEvent(event);
alert("Client coordinates:" + event.clientX + "," + event.clientY);
});
- 页面坐标位置
页面坐标通过事件对象的pageX 和pageY 属性,能告诉你事件是在页面中的什么位置发生的 - 屏幕坐标位置
过screenX 和screenY 属性就可以确定鼠标事件发生时鼠标指针相对于整个屏幕的坐标信息
…………更多内容参考红皮书
五、内存和性能
1、事件委托
假设如果我们为页面上的每一个需要事件的元素都添加事件处理程序,那么会严重影响页面的整体性能。原因有一下几点:
- 每个函数都是对象,都会占用内存
- 必须事先指定所有事件处理程序而导致的DOM访问次数,会延迟整个页面的交互就绪时间
所以我们使用事件委托的方式,其原理就是使用事件的冒泡机制。具体看例子
平常的操作:
- Go somewhere
- Do something
- Say hi
利用事件委托:
- Go somewhere
- Do something
- Say hi
这样我们只需要把事件处理程序放到ul这一个DOM节点上,然后根据target的值来判断用哪个事件,就可以避免页面中写大量的事件处理程序(event handler)了,也减少了DOM的操作。
2、移除event handler
事件委托是限制event handler的数量。另外可以通过移除不用的空事件处理程序(dangling event handler)来提高性能。
造成dangling event handler的原因可能有两种:
- 从文档中移除带有事件处理程序的元素时,这可能是通过纯粹的DOM操作,例如使用removeChild()和replaceChild()方法
- 使用innerHTML 替换页面中某一部分的时候。如果带有事件处理程序的元素被innerHTML 删除了,那么原来添加到元素中的事件处理程序极有可能无法被当作垃圾回收
当按钮被从页面中移除时,它还带着一个事件处理程,仍然占用着内存。
解决办法:
建议浏览器卸载页面之前移除页面中的所有事件处理程序。